From 429222a46c13928b8b6da8323c0378c7b0a2028f Mon Sep 17 00:00:00 2001 From: Lukas K Date: Sun, 29 Jan 2017 20:25:18 +0100 Subject: [PATCH] initial import --- .gitignore | 16 + Makefile | 245 + README.md | 210 + block/block.cpp | 177 + block/block.hpp | 45 + block/bus.cpp | 60 + block/bus.hpp | 41 + block/component.cpp | 82 + block/component.hpp | 56 + block/net.cpp | 36 + block/net.hpp | 34 + board/board.cpp | 563 + board/board.hpp | 65 + board/board_package.cpp | 31 + board/board_package.hpp | 35 + board/track.cpp | 164 + board/track.hpp | 68 + board/via.cpp | 24 + board/via.hpp | 31 + board/via_padstack_provider.cpp | 41 + board/via_padstack_provider.hpp | 41 + canvas/box_selection.cpp | 193 + canvas/box_selection.hpp | 37 + canvas/canvas.cpp | 69 + canvas/canvas.hpp | 210 + canvas/canvas_cairo.cpp | 22 + canvas/canvas_cairo.hpp | 18 + canvas/canvas_gl.cpp | 281 + canvas/draw.cpp | 123 + canvas/gl_util.cpp | 106 + canvas/gl_util.hpp | 13 + canvas/grid.cpp | 152 + canvas/grid.hpp | 34 + canvas/hershey_fonts.cpp | 3352 +++++ canvas/hover_prelight.cpp | 34 + canvas/image.cpp | 49 + canvas/layer_display.hpp | 16 + canvas/pan.cpp | 97 + canvas/polypartition/polypartition.cpp | 1552 +++ canvas/polypartition/polypartition.h | 357 + canvas/render.cpp | 911 ++ canvas/selectables.cpp | 104 + canvas/selectables.hpp | 135 + canvas/selection_filter.cpp | 16 + canvas/selection_filter.hpp | 17 + canvas/shaders/grid-fragment.glsl | 7 + canvas/shaders/grid-vertex.glsl | 18 + canvas/shaders/selectable-fragment.glsl | 7 + canvas/shaders/selectable-geometry.glsl | 114 + canvas/shaders/selectable-vertex.glsl | 17 + canvas/shaders/selection-fragment.glsl | 17 + canvas/shaders/selection-vertex.glsl | 33 + canvas/shaders/triangle-fragment.glsl | 38 + canvas/shaders/triangle-geometry.glsl | 105 + canvas/shaders/triangle-vertex.glsl | 21 + canvas/target.hpp | 32 + canvas/text.cpp | 365 + canvas/triangle.cpp | 110 + canvas/triangle.hpp | 57 + clipper/clipper.cpp | 4622 +++++++ clipper/clipper.hpp | 404 + common/arc.cpp | 34 + common/arc.hpp | 32 + common/common.hpp | 91 + common/hole.cpp | 28 + common/hole.hpp | 30 + common/junction.cpp | 29 + common/junction.hpp | 44 + common/line.cpp | 27 + common/line.hpp | 30 + common/lut.hpp | 28 + common/polygon.cpp | 137 + common/polygon.hpp | 51 + common/text.cpp | 51 + common/text.hpp | 40 + constraints/clearance.cpp | 23 + constraints/clearance.hpp | 23 + constraints/constraints.cpp | 71 + constraints/constraints.hpp | 28 + constraints/net_class.cpp | 24 + constraints/net_class.hpp | 21 + core/buffer.cpp | 191 + core/buffer.hpp | 44 + core/clipboard.cpp | 65 + core/clipboard.hpp | 21 + core/core.cpp | 384 + core/core.hpp | 180 + core/core_board.cpp | 427 + core/core_board.hpp | 73 + core/core_package.cpp | 195 + core/core_package.hpp | 62 + core/core_padstack.cpp | 108 + core/core_padstack.hpp | 44 + core/core_properties.cpp | 244 + core/core_schematic.cpp | 516 + core/core_schematic.hpp | 92 + core/core_symbol.cpp | 292 + core/core_symbol.hpp | 68 + core/cores.cpp | 17 + core/cores.hpp | 16 + core/tool.cpp | 0 core/tool_add_component.cpp | 39 + core/tool_add_component.hpp | 12 + core/tool_add_part.cpp | 42 + core/tool_add_part.hpp | 12 + core/tool_assign_part.cpp | 68 + core/tool_assign_part.hpp | 20 + core/tool_bend_line_net.cpp | 64 + core/tool_bend_line_net.hpp | 19 + core/tool_catalog.cpp | 45 + core/tool_catalog.hpp | 14 + core/tool_delete.cpp | 296 + core/tool_delete.hpp | 16 + core/tool_disconnect.cpp | 33 + core/tool_disconnect.hpp | 17 + core/tool_drag_keep_slope.cpp | 129 + core/tool_drag_keep_slope.hpp | 33 + core/tool_draw_arc.cpp | 98 + core/tool_draw_arc.hpp | 23 + core/tool_draw_line.cpp | 74 + core/tool_draw_line.hpp | 18 + core/tool_draw_line_net.cpp | 351 + core/tool_draw_line_net.hpp | 24 + core/tool_draw_polygon.cpp | 82 + core/tool_draw_polygon.hpp | 21 + core/tool_draw_track.cpp | 176 + core/tool_draw_track.hpp | 19 + core/tool_edit_component_pin_names.cpp | 52 + core/tool_edit_component_pin_names.hpp | 19 + core/tool_enter_datum.cpp | 232 + core/tool_enter_datum.hpp | 17 + core/tool_manage_buses.cpp | 35 + core/tool_manage_buses.hpp | 18 + core/tool_map_package.cpp | 55 + core/tool_map_package.hpp | 14 + core/tool_map_pin.cpp | 155 + core/tool_map_pin.hpp | 19 + core/tool_map_symbol.cpp | 89 + core/tool_map_symbol.hpp | 13 + core/tool_move.cpp | 389 + core/tool_move.hpp | 24 + core/tool_move_net_segment.cpp | 102 + core/tool_move_net_segment.hpp | 18 + core/tool_paste.cpp | 143 + core/tool_paste.hpp | 22 + core/tool_place_bus_label.cpp | 80 + core/tool_place_bus_label.hpp | 26 + core/tool_place_bus_ripper.cpp | 98 + core/tool_place_bus_ripper.hpp | 17 + core/tool_place_hole.cpp | 58 + core/tool_place_hole.hpp | 22 + core/tool_place_junction.cpp | 85 + core/tool_place_junction.hpp | 28 + core/tool_place_net_label.cpp | 118 + core/tool_place_net_label.hpp | 24 + core/tool_place_pad.cpp | 64 + core/tool_place_pad.hpp | 18 + core/tool_place_power_symbol.cpp | 115 + core/tool_place_power_symbol.hpp | 28 + core/tool_place_text.cpp | 53 + core/tool_place_text.hpp | 20 + core/tool_place_via.cpp | 51 + core/tool_place_via.hpp | 26 + core/tool_route_track.cpp | 402 + core/tool_route_track.hpp | 38 + core/tool_smash.cpp | 36 + core/tool_smash.hpp | 17 + dialogs/annotate.cpp | 116 + dialogs/annotate.hpp | 26 + dialogs/ask_datum.cpp | 56 + dialogs/ask_datum.hpp | 24 + dialogs/ask_delete_component.cpp | 25 + dialogs/ask_delete_component.hpp | 21 + dialogs/ask_net_merge.cpp | 34 + dialogs/ask_net_merge.hpp | 23 + dialogs/component_pin_names.cpp | 118 + dialogs/component_pin_names.hpp | 23 + dialogs/dialogs.cpp | 219 + dialogs/dialogs.hpp | 45 + dialogs/manage_buses.cpp | 258 + dialogs/manage_buses.hpp | 28 + dialogs/map_package.cpp | 60 + dialogs/map_package.hpp | 38 + dialogs/map_pin.cpp | 58 + dialogs/map_pin.hpp | 37 + dialogs/map_symbol.cpp | 57 + dialogs/map_symbol.hpp | 36 + dialogs/pool_browser.ui | 96 + dialogs/pool_browser_box.cpp | 25 + dialogs/pool_browser_box.hpp | 18 + dialogs/pool_browser_entity.cpp | 111 + dialogs/pool_browser_entity.hpp | 50 + dialogs/pool_browser_package.cpp | 108 + dialogs/pool_browser_package.hpp | 47 + dialogs/pool_browser_padstack.cpp | 74 + dialogs/pool_browser_padstack.hpp | 44 + dialogs/pool_browser_part.cpp | 171 + dialogs/pool_browser_part.hpp | 52 + dialogs/pool_browser_part.ui | 119 + dialogs/pool_browser_symbol.cpp | 101 + dialogs/pool_browser_symbol.hpp | 49 + dialogs/select_net.cpp | 43 + dialogs/select_net.hpp | 27 + dialogs/select_via_padstack.cpp | 58 + dialogs/select_via_padstack.hpp | 37 + export_gerber/cam_job.cpp | 32 + export_gerber/cam_job.hpp | 19 + export_gerber/canvas_gerber.cpp | 50 + export_gerber/canvas_gerber.hpp | 20 + export_gerber/excellon_writer.cpp | 68 + export_gerber/excellon_writer.hpp | 35 + export_gerber/gerber_export.cpp | 61 + export_gerber/gerber_export.hpp | 28 + export_gerber/gerber_writer.cpp | 129 + export_gerber/gerber_writer.hpp | 57 + export_pdf.cpp | 24 + export_pdf.hpp | 7 + imp.gresource.xml | 33 + imp/cam_job.ui | 182 + imp/cam_job_dialog.cpp | 61 + imp/cam_job_dialog.hpp | 32 + imp/footprint_generator/dual.svg | 474 + .../footprint_generator.ui | 47 + .../footprint_generator_base.cpp | 29 + .../footprint_generator_base.hpp | 27 + .../footprint_generator_dual.cpp | 198 + .../footprint_generator_dual.hpp | 18 + .../footprint_generator_quad.cpp | 202 + .../footprint_generator_quad.hpp | 20 + .../footprint_generator_single.cpp | 83 + .../footprint_generator_single.hpp | 16 + .../footprint_generator_window.cpp | 66 + .../footprint_generator_window.hpp | 25 + imp/footprint_generator/quad.svg | 856 ++ imp/footprint_generator/single.svg | 295 + imp/footprint_generator/svg_overlay.cpp | 78 + imp/footprint_generator/svg_overlay.hpp | 23 + imp/imp.cpp | 417 + imp/imp.hpp | 60 + imp/imp_board.cpp | 68 + imp/imp_board.hpp | 25 + imp/imp_layer.cpp | 31 + imp/imp_layer.hpp | 21 + imp/imp_main.cpp | 107 + imp/imp_package.cpp | 84 + imp/imp_package.hpp | 19 + imp/imp_padstack.cpp | 50 + imp/imp_padstack.hpp | 18 + imp/imp_schematic.cpp | 129 + imp/imp_schematic.hpp | 28 + imp/imp_symbol.cpp | 46 + imp/imp_symbol.hpp | 19 + imp/key_sequence.cpp | 69 + imp/key_sequence.hpp | 33 + imp/keyseq_dialog.cpp | 58 + imp/keyseq_dialog.hpp | 17 + imp/main_window.cpp | 28 + imp/main_window.hpp | 28 + imp/selection_filter_dialog.cpp | 80 + imp/selection_filter_dialog.hpp | 22 + imp/tool_popover.cpp | 97 + imp/tool_popover.hpp | 37 + json.hpp | 10626 ++++++++++++++++ json_fwd.hpp | 30 + layer.hpp | 15 + object.hpp | 32 + object_descr.cpp | 85 + object_descr.hpp | 33 + obstacle/canvas_obstacle.cpp | 52 + obstacle/canvas_obstacle.hpp | 24 + package/pad.cpp | 29 + package/pad.hpp | 35 + .../pool-update-parametric.cpp | 87 + pool-update-parametric/schema.sql | 14 + pool-update/pool-update.cpp | 264 + pool-update/schema.sql | 50 + pool-util/part-editor.cpp | 605 + pool-util/part-editor.hpp | 65 + pool-util/part-editor.ui | 503 + pool-util/util_main.cpp | 149 + pool/entity.cpp | 78 + pool/entity.hpp | 32 + pool/gate.cpp | 50 + pool/gate.hpp | 30 + pool/package.cpp | 170 + pool/package.hpp | 52 + pool/padstack.cpp | 102 + pool/padstack.hpp | 41 + pool/part.cpp | 150 + pool/part.hpp | 53 + pool/pool.cpp | 85 + pool/pool.hpp | 43 + pool/symbol.cpp | 212 + pool/symbol.hpp | 74 + pool/unit.cpp | 118 + pool/unit.hpp | 48 + prj-util/util_main.cpp | 72 + property_panels/property_editor.cpp | 240 + property_panels/property_editor.hpp | 112 + property_panels/property_editors.cpp | 67 + property_panels/property_editors.hpp | 24 + property_panels/property_panel.cpp | 100 + property_panels/property_panel.hpp | 32 + property_panels/property_panel.ui | 105 + property_panels/property_panels.cpp | 66 + property_panels/property_panels.hpp | 21 + schematic/bus_label.cpp | 37 + schematic/bus_label.hpp | 36 + schematic/bus_ripper.cpp | 50 + schematic/bus_ripper.hpp | 39 + schematic/frame.cpp | 24 + schematic/frame.hpp | 30 + schematic/line_net.cpp | 185 + schematic/line_net.hpp | 70 + schematic/net_label.cpp | 34 + schematic/net_label.hpp | 34 + schematic/power_symbol.cpp | 36 + schematic/power_symbol.hpp | 38 + schematic/schematic.cpp | 690 + schematic/schematic.hpp | 62 + schematic/schematic_symbol.cpp | 72 + schematic/schematic_symbol.hpp | 39 + schematic/sheet.cpp | 504 + schematic/sheet.hpp | 74 + util/accumulator.hpp | 21 + util/placement.cpp | 61 + util/placement.hpp | 55 + util/placement_provider.hpp | 10 + util/position_provider.hpp | 12 + util/sort_controller.cpp | 93 + util/sort_controller.hpp | 28 + util/sqlite.cpp | 90 + util/sqlite.hpp | 70 + util/util.cpp | 16 + util/util.hpp | 7 + util/uuid.cpp | 41 + util/uuid.hpp | 34 + util/uuid_path.cpp | 5 + util/uuid_path.hpp | 57 + util/uuid_provider.hpp | 10 + util/uuid_ptr.hpp | 55 + util/uuid_win32.cpp | 69 + util/uuid_win32.hpp | 9 + util/warning.hpp | 13 + widgets/chooser_buttons.cpp | 31 + widgets/chooser_buttons.hpp | 22 + widgets/layer_box.cpp | 353 + widgets/layer_box.hpp | 68 + widgets/net_button.cpp | 51 + widgets/net_button.hpp | 29 + widgets/net_selector.cpp | 108 + widgets/net_selector.hpp | 50 + widgets/sheet_box.cpp | 136 + widgets/sheet_box.hpp | 50 + widgets/spin_button_dim.cpp | 33 + widgets/spin_button_dim.hpp | 13 + widgets/warnings_box.cpp | 56 + widgets/warnings_box.hpp | 35 + window.ui | 196 + 359 files changed, 50730 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 block/block.cpp create mode 100644 block/block.hpp create mode 100644 block/bus.cpp create mode 100644 block/bus.hpp create mode 100644 block/component.cpp create mode 100644 block/component.hpp create mode 100644 block/net.cpp create mode 100644 block/net.hpp create mode 100644 board/board.cpp create mode 100644 board/board.hpp create mode 100644 board/board_package.cpp create mode 100644 board/board_package.hpp create mode 100644 board/track.cpp create mode 100644 board/track.hpp create mode 100644 board/via.cpp create mode 100644 board/via.hpp create mode 100644 board/via_padstack_provider.cpp create mode 100644 board/via_padstack_provider.hpp create mode 100644 canvas/box_selection.cpp create mode 100644 canvas/box_selection.hpp create mode 100644 canvas/canvas.cpp create mode 100644 canvas/canvas.hpp create mode 100644 canvas/canvas_cairo.cpp create mode 100644 canvas/canvas_cairo.hpp create mode 100644 canvas/canvas_gl.cpp create mode 100644 canvas/draw.cpp create mode 100644 canvas/gl_util.cpp create mode 100644 canvas/gl_util.hpp create mode 100644 canvas/grid.cpp create mode 100644 canvas/grid.hpp create mode 100644 canvas/hershey_fonts.cpp create mode 100644 canvas/hover_prelight.cpp create mode 100644 canvas/image.cpp create mode 100644 canvas/layer_display.hpp create mode 100644 canvas/pan.cpp create mode 100644 canvas/polypartition/polypartition.cpp create mode 100644 canvas/polypartition/polypartition.h create mode 100644 canvas/render.cpp create mode 100644 canvas/selectables.cpp create mode 100644 canvas/selectables.hpp create mode 100644 canvas/selection_filter.cpp create mode 100644 canvas/selection_filter.hpp create mode 100644 canvas/shaders/grid-fragment.glsl create mode 100644 canvas/shaders/grid-vertex.glsl create mode 100644 canvas/shaders/selectable-fragment.glsl create mode 100644 canvas/shaders/selectable-geometry.glsl create mode 100644 canvas/shaders/selectable-vertex.glsl create mode 100644 canvas/shaders/selection-fragment.glsl create mode 100644 canvas/shaders/selection-vertex.glsl create mode 100644 canvas/shaders/triangle-fragment.glsl create mode 100644 canvas/shaders/triangle-geometry.glsl create mode 100644 canvas/shaders/triangle-vertex.glsl create mode 100644 canvas/target.hpp create mode 100644 canvas/text.cpp create mode 100644 canvas/triangle.cpp create mode 100644 canvas/triangle.hpp create mode 100644 clipper/clipper.cpp create mode 100644 clipper/clipper.hpp create mode 100644 common/arc.cpp create mode 100644 common/arc.hpp create mode 100644 common/common.hpp create mode 100644 common/hole.cpp create mode 100644 common/hole.hpp create mode 100644 common/junction.cpp create mode 100644 common/junction.hpp create mode 100644 common/line.cpp create mode 100644 common/line.hpp create mode 100644 common/lut.hpp create mode 100644 common/polygon.cpp create mode 100644 common/polygon.hpp create mode 100644 common/text.cpp create mode 100644 common/text.hpp create mode 100644 constraints/clearance.cpp create mode 100644 constraints/clearance.hpp create mode 100644 constraints/constraints.cpp create mode 100644 constraints/constraints.hpp create mode 100644 constraints/net_class.cpp create mode 100644 constraints/net_class.hpp create mode 100644 core/buffer.cpp create mode 100644 core/buffer.hpp create mode 100644 core/clipboard.cpp create mode 100644 core/clipboard.hpp create mode 100644 core/core.cpp create mode 100644 core/core.hpp create mode 100644 core/core_board.cpp create mode 100644 core/core_board.hpp create mode 100644 core/core_package.cpp create mode 100644 core/core_package.hpp create mode 100644 core/core_padstack.cpp create mode 100644 core/core_padstack.hpp create mode 100644 core/core_properties.cpp create mode 100644 core/core_schematic.cpp create mode 100644 core/core_schematic.hpp create mode 100644 core/core_symbol.cpp create mode 100644 core/core_symbol.hpp create mode 100644 core/cores.cpp create mode 100644 core/cores.hpp create mode 100644 core/tool.cpp create mode 100644 core/tool_add_component.cpp create mode 100644 core/tool_add_component.hpp create mode 100644 core/tool_add_part.cpp create mode 100644 core/tool_add_part.hpp create mode 100644 core/tool_assign_part.cpp create mode 100644 core/tool_assign_part.hpp create mode 100644 core/tool_bend_line_net.cpp create mode 100644 core/tool_bend_line_net.hpp create mode 100644 core/tool_catalog.cpp create mode 100644 core/tool_catalog.hpp create mode 100644 core/tool_delete.cpp create mode 100644 core/tool_delete.hpp create mode 100644 core/tool_disconnect.cpp create mode 100644 core/tool_disconnect.hpp create mode 100644 core/tool_drag_keep_slope.cpp create mode 100644 core/tool_drag_keep_slope.hpp create mode 100644 core/tool_draw_arc.cpp create mode 100644 core/tool_draw_arc.hpp create mode 100644 core/tool_draw_line.cpp create mode 100644 core/tool_draw_line.hpp create mode 100644 core/tool_draw_line_net.cpp create mode 100644 core/tool_draw_line_net.hpp create mode 100644 core/tool_draw_polygon.cpp create mode 100644 core/tool_draw_polygon.hpp create mode 100644 core/tool_draw_track.cpp create mode 100644 core/tool_draw_track.hpp create mode 100644 core/tool_edit_component_pin_names.cpp create mode 100644 core/tool_edit_component_pin_names.hpp create mode 100644 core/tool_enter_datum.cpp create mode 100644 core/tool_enter_datum.hpp create mode 100644 core/tool_manage_buses.cpp create mode 100644 core/tool_manage_buses.hpp create mode 100644 core/tool_map_package.cpp create mode 100644 core/tool_map_package.hpp create mode 100644 core/tool_map_pin.cpp create mode 100644 core/tool_map_pin.hpp create mode 100644 core/tool_map_symbol.cpp create mode 100644 core/tool_map_symbol.hpp create mode 100644 core/tool_move.cpp create mode 100644 core/tool_move.hpp create mode 100644 core/tool_move_net_segment.cpp create mode 100644 core/tool_move_net_segment.hpp create mode 100644 core/tool_paste.cpp create mode 100644 core/tool_paste.hpp create mode 100644 core/tool_place_bus_label.cpp create mode 100644 core/tool_place_bus_label.hpp create mode 100644 core/tool_place_bus_ripper.cpp create mode 100644 core/tool_place_bus_ripper.hpp create mode 100644 core/tool_place_hole.cpp create mode 100644 core/tool_place_hole.hpp create mode 100644 core/tool_place_junction.cpp create mode 100644 core/tool_place_junction.hpp create mode 100644 core/tool_place_net_label.cpp create mode 100644 core/tool_place_net_label.hpp create mode 100644 core/tool_place_pad.cpp create mode 100644 core/tool_place_pad.hpp create mode 100644 core/tool_place_power_symbol.cpp create mode 100644 core/tool_place_power_symbol.hpp create mode 100644 core/tool_place_text.cpp create mode 100644 core/tool_place_text.hpp create mode 100644 core/tool_place_via.cpp create mode 100644 core/tool_place_via.hpp create mode 100644 core/tool_route_track.cpp create mode 100644 core/tool_route_track.hpp create mode 100644 core/tool_smash.cpp create mode 100644 core/tool_smash.hpp create mode 100644 dialogs/annotate.cpp create mode 100644 dialogs/annotate.hpp create mode 100644 dialogs/ask_datum.cpp create mode 100644 dialogs/ask_datum.hpp create mode 100644 dialogs/ask_delete_component.cpp create mode 100644 dialogs/ask_delete_component.hpp create mode 100644 dialogs/ask_net_merge.cpp create mode 100644 dialogs/ask_net_merge.hpp create mode 100644 dialogs/component_pin_names.cpp create mode 100644 dialogs/component_pin_names.hpp create mode 100644 dialogs/dialogs.cpp create mode 100644 dialogs/dialogs.hpp create mode 100644 dialogs/manage_buses.cpp create mode 100644 dialogs/manage_buses.hpp create mode 100644 dialogs/map_package.cpp create mode 100644 dialogs/map_package.hpp create mode 100644 dialogs/map_pin.cpp create mode 100644 dialogs/map_pin.hpp create mode 100644 dialogs/map_symbol.cpp create mode 100644 dialogs/map_symbol.hpp create mode 100644 dialogs/pool_browser.ui create mode 100644 dialogs/pool_browser_box.cpp create mode 100644 dialogs/pool_browser_box.hpp create mode 100644 dialogs/pool_browser_entity.cpp create mode 100644 dialogs/pool_browser_entity.hpp create mode 100644 dialogs/pool_browser_package.cpp create mode 100644 dialogs/pool_browser_package.hpp create mode 100644 dialogs/pool_browser_padstack.cpp create mode 100644 dialogs/pool_browser_padstack.hpp create mode 100644 dialogs/pool_browser_part.cpp create mode 100644 dialogs/pool_browser_part.hpp create mode 100644 dialogs/pool_browser_part.ui create mode 100644 dialogs/pool_browser_symbol.cpp create mode 100644 dialogs/pool_browser_symbol.hpp create mode 100644 dialogs/select_net.cpp create mode 100644 dialogs/select_net.hpp create mode 100644 dialogs/select_via_padstack.cpp create mode 100644 dialogs/select_via_padstack.hpp create mode 100644 export_gerber/cam_job.cpp create mode 100644 export_gerber/cam_job.hpp create mode 100644 export_gerber/canvas_gerber.cpp create mode 100644 export_gerber/canvas_gerber.hpp create mode 100644 export_gerber/excellon_writer.cpp create mode 100644 export_gerber/excellon_writer.hpp create mode 100644 export_gerber/gerber_export.cpp create mode 100644 export_gerber/gerber_export.hpp create mode 100644 export_gerber/gerber_writer.cpp create mode 100644 export_gerber/gerber_writer.hpp create mode 100644 export_pdf.cpp create mode 100644 export_pdf.hpp create mode 100644 imp.gresource.xml create mode 100644 imp/cam_job.ui create mode 100644 imp/cam_job_dialog.cpp create mode 100644 imp/cam_job_dialog.hpp create mode 100644 imp/footprint_generator/dual.svg create mode 100644 imp/footprint_generator/footprint_generator.ui create mode 100644 imp/footprint_generator/footprint_generator_base.cpp create mode 100644 imp/footprint_generator/footprint_generator_base.hpp create mode 100644 imp/footprint_generator/footprint_generator_dual.cpp create mode 100644 imp/footprint_generator/footprint_generator_dual.hpp create mode 100644 imp/footprint_generator/footprint_generator_quad.cpp create mode 100644 imp/footprint_generator/footprint_generator_quad.hpp create mode 100644 imp/footprint_generator/footprint_generator_single.cpp create mode 100644 imp/footprint_generator/footprint_generator_single.hpp create mode 100644 imp/footprint_generator/footprint_generator_window.cpp create mode 100644 imp/footprint_generator/footprint_generator_window.hpp create mode 100644 imp/footprint_generator/quad.svg create mode 100644 imp/footprint_generator/single.svg create mode 100644 imp/footprint_generator/svg_overlay.cpp create mode 100644 imp/footprint_generator/svg_overlay.hpp create mode 100644 imp/imp.cpp create mode 100644 imp/imp.hpp create mode 100644 imp/imp_board.cpp create mode 100644 imp/imp_board.hpp create mode 100644 imp/imp_layer.cpp create mode 100644 imp/imp_layer.hpp create mode 100644 imp/imp_main.cpp create mode 100644 imp/imp_package.cpp create mode 100644 imp/imp_package.hpp create mode 100644 imp/imp_padstack.cpp create mode 100644 imp/imp_padstack.hpp create mode 100644 imp/imp_schematic.cpp create mode 100644 imp/imp_schematic.hpp create mode 100644 imp/imp_symbol.cpp create mode 100644 imp/imp_symbol.hpp create mode 100644 imp/key_sequence.cpp create mode 100644 imp/key_sequence.hpp create mode 100644 imp/keyseq_dialog.cpp create mode 100644 imp/keyseq_dialog.hpp create mode 100644 imp/main_window.cpp create mode 100644 imp/main_window.hpp create mode 100644 imp/selection_filter_dialog.cpp create mode 100644 imp/selection_filter_dialog.hpp create mode 100644 imp/tool_popover.cpp create mode 100644 imp/tool_popover.hpp create mode 100644 json.hpp create mode 100644 json_fwd.hpp create mode 100644 layer.hpp create mode 100644 object.hpp create mode 100644 object_descr.cpp create mode 100644 object_descr.hpp create mode 100644 obstacle/canvas_obstacle.cpp create mode 100644 obstacle/canvas_obstacle.hpp create mode 100644 package/pad.cpp create mode 100644 package/pad.hpp create mode 100644 pool-update-parametric/pool-update-parametric.cpp create mode 100644 pool-update-parametric/schema.sql create mode 100644 pool-update/pool-update.cpp create mode 100644 pool-update/schema.sql create mode 100644 pool-util/part-editor.cpp create mode 100644 pool-util/part-editor.hpp create mode 100644 pool-util/part-editor.ui create mode 100644 pool-util/util_main.cpp create mode 100644 pool/entity.cpp create mode 100644 pool/entity.hpp create mode 100644 pool/gate.cpp create mode 100644 pool/gate.hpp create mode 100644 pool/package.cpp create mode 100644 pool/package.hpp create mode 100644 pool/padstack.cpp create mode 100644 pool/padstack.hpp create mode 100644 pool/part.cpp create mode 100644 pool/part.hpp create mode 100644 pool/pool.cpp create mode 100644 pool/pool.hpp create mode 100644 pool/symbol.cpp create mode 100644 pool/symbol.hpp create mode 100644 pool/unit.cpp create mode 100644 pool/unit.hpp create mode 100644 prj-util/util_main.cpp create mode 100644 property_panels/property_editor.cpp create mode 100644 property_panels/property_editor.hpp create mode 100644 property_panels/property_editors.cpp create mode 100644 property_panels/property_editors.hpp create mode 100644 property_panels/property_panel.cpp create mode 100644 property_panels/property_panel.hpp create mode 100644 property_panels/property_panel.ui create mode 100644 property_panels/property_panels.cpp create mode 100644 property_panels/property_panels.hpp create mode 100644 schematic/bus_label.cpp create mode 100644 schematic/bus_label.hpp create mode 100644 schematic/bus_ripper.cpp create mode 100644 schematic/bus_ripper.hpp create mode 100644 schematic/frame.cpp create mode 100644 schematic/frame.hpp create mode 100644 schematic/line_net.cpp create mode 100644 schematic/line_net.hpp create mode 100644 schematic/net_label.cpp create mode 100644 schematic/net_label.hpp create mode 100644 schematic/power_symbol.cpp create mode 100644 schematic/power_symbol.hpp create mode 100644 schematic/schematic.cpp create mode 100644 schematic/schematic.hpp create mode 100644 schematic/schematic_symbol.cpp create mode 100644 schematic/schematic_symbol.hpp create mode 100644 schematic/sheet.cpp create mode 100644 schematic/sheet.hpp create mode 100644 util/accumulator.hpp create mode 100644 util/placement.cpp create mode 100644 util/placement.hpp create mode 100644 util/placement_provider.hpp create mode 100644 util/position_provider.hpp create mode 100644 util/sort_controller.cpp create mode 100644 util/sort_controller.hpp create mode 100644 util/sqlite.cpp create mode 100644 util/sqlite.hpp create mode 100644 util/util.cpp create mode 100644 util/util.hpp create mode 100644 util/uuid.cpp create mode 100644 util/uuid.hpp create mode 100644 util/uuid_path.cpp create mode 100644 util/uuid_path.hpp create mode 100644 util/uuid_provider.hpp create mode 100644 util/uuid_ptr.hpp create mode 100644 util/uuid_win32.cpp create mode 100644 util/uuid_win32.hpp create mode 100644 util/warning.hpp create mode 100644 widgets/chooser_buttons.cpp create mode 100644 widgets/chooser_buttons.hpp create mode 100644 widgets/layer_box.cpp create mode 100644 widgets/layer_box.hpp create mode 100644 widgets/net_button.cpp create mode 100644 widgets/net_button.hpp create mode 100644 widgets/net_selector.cpp create mode 100644 widgets/net_selector.hpp create mode 100644 widgets/sheet_box.cpp create mode 100644 widgets/sheet_box.hpp create mode 100644 widgets/spin_button_dim.cpp create mode 100644 widgets/spin_button_dim.hpp create mode 100644 widgets/warnings_box.cpp create mode 100644 widgets/warnings_box.hpp create mode 100644 window.ui diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..3569b903e --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +main +horizon-imp +horizon-pool +horizon-pool-update +horizon-pool-update-parametric +horizon-prj +*.o +*.d +*~ +.gdb_history +resources.cpp +.cproject +.settings +.project +example/pool/*.db +\#*.ui\# diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..547e78d0b --- /dev/null +++ b/Makefile @@ -0,0 +1,245 @@ +CC=g++ +PKGCONFIG=pkg-config + +all: horizon-imp horizon-pool horizon-pool-update horizon-prj horizon-pool-update-parametric + +SRC_COMMON = \ + util/uuid.cpp \ + util/uuid_path.cpp\ + util/sqlite.cpp\ + pool/unit.cpp \ + pool/symbol.cpp \ + pool/part.cpp\ + common/junction.cpp \ + common/line.cpp \ + common/arc.cpp \ + pool/gate.cpp\ + block/net.cpp\ + block/bus.cpp\ + block/block.cpp\ + pool/entity.cpp\ + block/component.cpp\ + schematic/schematic.cpp\ + schematic/sheet.cpp\ + common/text.cpp\ + schematic/line_net.cpp\ + schematic/net_label.cpp\ + schematic/bus_label.cpp\ + schematic/bus_ripper.cpp\ + schematic/schematic_symbol.cpp\ + schematic/power_symbol.cpp\ + schematic/frame.cpp\ + pool/padstack.cpp\ + common/polygon.cpp\ + common/hole.cpp\ + pool/package.cpp\ + package/pad.cpp\ + board/board.cpp\ + board/board_package.cpp\ + board/track.cpp\ + board/via_padstack_provider.cpp\ + board/via.cpp\ + pool/pool.cpp \ + util/placement.cpp\ + util/util.cpp\ + object_descr.cpp\ + constraints/net_class.cpp\ + constraints/constraints.cpp\ + constraints/clearance.cpp \ + resources.cpp\ + +ifeq ($(OS),Windows_NT) + SRC_COMMON += util/uuid_win32.cpp +endif + +SRC_IMP = \ + imp/imp_main.cpp \ + imp/main_window.cpp \ + imp/imp.cpp \ + imp/imp_layer.cpp\ + imp/imp_symbol.cpp\ + imp/imp_schematic.cpp\ + imp/imp_padstack.cpp\ + imp/imp_package.cpp\ + imp/imp_board.cpp\ + imp/tool_popover.cpp\ + imp/selection_filter_dialog.cpp\ + imp/cam_job_dialog.cpp\ + canvas/canvas.cpp \ + canvas/canvas_gl.cpp \ + canvas/canvas_cairo.cpp \ + canvas/grid.cpp \ + canvas/gl_util.cpp \ + canvas/pan.cpp \ + canvas/render.cpp \ + canvas/draw.cpp \ + canvas/text.cpp \ + canvas/hershey_fonts.cpp \ + canvas/box_selection.cpp \ + canvas/selectables.cpp \ + canvas/hover_prelight.cpp\ + canvas/triangle.cpp\ + canvas/image.cpp\ + canvas/selection_filter.cpp\ + canvas/polypartition/polypartition.cpp\ + core/core.cpp \ + core/core_properties.cpp\ + core/tool_catalog.cpp\ + core/tool_move.cpp\ + core/tool_place_junction.cpp\ + core/tool_draw_line.cpp\ + core/tool_delete.cpp\ + core/tool_draw_arc.cpp\ + core/tool_map_pin.cpp\ + core/tool_map_symbol.cpp\ + core/tool_draw_line_net.cpp\ + core/tool_add_component.cpp\ + core/tool_place_text.cpp\ + core/tool_place_net_label.cpp\ + core/tool_disconnect.cpp\ + core/tool_bend_line_net.cpp\ + core/tool_move_net_segment.cpp\ + core/tool_place_power_symbol.cpp\ + core/tool_edit_component_pin_names.cpp\ + core/tool_place_bus_label.cpp\ + core/tool_place_bus_ripper.cpp\ + core/tool_manage_buses.cpp\ + core/tool_draw_polygon.cpp\ + core/tool_enter_datum.cpp\ + core/tool_place_hole.cpp\ + core/tool_place_pad.cpp\ + core/tool_paste.cpp\ + core/tool_assign_part.cpp\ + core/tool_map_package.cpp\ + core/tool_draw_track.cpp\ + core/tool_place_via.cpp\ + core/tool_route_track.cpp\ + core/tool_drag_keep_slope.cpp\ + core/tool_add_part.cpp\ + core/tool_smash.cpp\ + core/cores.cpp\ + core/clipboard.cpp\ + core/buffer.cpp\ + dialogs/map_pin.cpp\ + dialogs/map_symbol.cpp\ + dialogs/map_package.cpp\ + dialogs/pool_browser_symbol.cpp\ + dialogs/pool_browser_entity.cpp\ + dialogs/pool_browser_padstack.cpp\ + dialogs/pool_browser_part.cpp\ + dialogs/ask_net_merge.cpp\ + dialogs/ask_delete_component.cpp\ + dialogs/select_net.cpp\ + dialogs/component_pin_names.cpp\ + dialogs/manage_buses.cpp\ + dialogs/ask_datum.cpp\ + dialogs/select_via_padstack.cpp\ + dialogs/dialogs.cpp\ + dialogs/pool_browser_box.cpp\ + dialogs/annotate.cpp\ + util/sort_controller.cpp\ + core/core_symbol.cpp\ + core/core_schematic.cpp\ + core/core_padstack.cpp\ + core/core_package.cpp\ + core/core_board.cpp\ + property_panels/property_panels.cpp\ + property_panels/property_panel.cpp\ + property_panels/property_editors.cpp\ + property_panels/property_editor.cpp\ + widgets/warnings_box.cpp\ + widgets/net_selector.cpp\ + widgets/sheet_box.cpp \ + widgets/net_button.cpp\ + widgets/layer_box.cpp\ + widgets/spin_button_dim.cpp\ + widgets/chooser_buttons.cpp\ + export_pdf.cpp\ + imp/key_sequence.cpp\ + imp/keyseq_dialog.cpp\ + clipper/clipper.cpp\ + obstacle/canvas_obstacle.cpp\ + export_gerber/gerber_writer.cpp\ + export_gerber/excellon_writer.cpp\ + export_gerber/gerber_export.cpp\ + export_gerber/canvas_gerber.cpp\ + export_gerber/cam_job.cpp\ + imp/footprint_generator/footprint_generator_window.cpp\ + imp/footprint_generator/footprint_generator_base.cpp\ + imp/footprint_generator/footprint_generator_dual.cpp\ + imp/footprint_generator/footprint_generator_single.cpp\ + imp/footprint_generator/footprint_generator_quad.cpp\ + imp/footprint_generator/svg_overlay.cpp + +SRC_POOL_UTIL = \ + pool-util/util_main.cpp\ + pool-util/part-editor.cpp\ + dialogs/pool_browser_entity.cpp\ + dialogs/pool_browser_package.cpp\ + dialogs/pool_browser_part.cpp\ + dialogs/pool_browser_box.cpp\ + util/sort_controller.cpp\ + +SRC_POOL_UPDATE = \ + pool-update/pool-update.cpp\ + +SRC_POOL_UPDATE_PARA = \ + pool-update-parametric/pool-update-parametric.cpp\ + +SRC_PRJ_UTIL = \ + prj-util/util_main.cpp + +SRC_ALL = $(sort $(SRC_COMMON) $(SRC_IMP) $(SRC_POOL_UTIL) $(SRC_POOL_UPDATE) $(SRC_PRJ_UTIL) $(SRC_POOL_UPDATE_PARA)) + +INC = -I. -Iblock -Iboard -Icommon -Iimp -Ipackage -Ipool -Ischematic -Iutil -Iconstraints + +DEFINES = -D_USE_MATH_DEFINES + +LIBS_COMMON = sqlite3 yaml-cpp +ifneq ($(OS),Windows_NT) + LIBS_COMMON += uuid +endif +LIBS_ALL = $(LIBS_COMMON) gtkmm-3.0 epoxy cairomm-pdf-1.0 librsvg-2.0 + +OPTIMIZE=-fdata-sections -ffunction-sections +DEBUG =-g3 +CFLAGS =$(DEBUG) $(DEFINES) $(OPTIMIZE) $(shell pkg-config --cflags $(LIBS_ALL)) -MP -MMD -pthread -Wall -Wshadow -std=c++14 +LDFLAGS = -lm -lpthread +GLIB_COMPILE_RESOURCES = $(shell $(PKGCONFIG) --variable=glib_compile_resources gio-2.0) + +ifeq ($(OS),Windows_NT) + LDFLAGS += -lrpcrt4 + DEFINES += -DWIN32_UUID +endif + +# Object files +OBJ_ALL = $(SRC_ALL:.cpp=.o) +OBJ_COMMON = $(SRC_COMMON:.cpp=.o) + +resources.cpp: imp.gresource.xml $(shell $(GLIB_COMPILE_RESOURCES) --sourcedir=. --generate-dependencies imp.gresource.xml) + $(GLIB_COMPILE_RESOURCES) imp.gresource.xml --target=$@ --sourcedir=. --generate-source + +horizon-imp: $(OBJ_COMMON) $(SRC_IMP:.cpp=.o) + $(CC) $^ $(LDFLAGS) $(shell $(PKGCONFIG) --libs $(LIBS_COMMON) gtkmm-3.0 epoxy cairomm-pdf-1.0 librsvg-2.0) -o $@ + +horizon-pool: $(OBJ_COMMON) $(SRC_POOL_UTIL:.cpp=.o) + $(CC) $^ $(LDFLAGS) $(shell $(PKGCONFIG) --libs $(LIBS_COMMON) gtkmm-3.0) -o $@ + +horizon-pool-update: $(OBJ_COMMON) $(SRC_POOL_UPDATE:.cpp=.o) + $(CC) $^ $(LDFLAGS) $(shell $(PKGCONFIG) --libs $(LIBS_COMMON) glibmm-2.4 giomm-2.4) -o $@ + +horizon-pool-update-parametric: $(OBJ_COMMON) $(SRC_POOL_UPDATE_PARA:.cpp=.o) + $(CC) $^ $(LDFLAGS) $(shell $(PKGCONFIG) --libs $(LIBS_COMMON) glibmm-2.4 giomm-2.4) -o $@ + +horizon-prj: $(OBJ_COMMON) $(SRC_PRJ_UTIL:.cpp=.o) + $(CC) $^ $(LDFLAGS) $(shell $(PKGCONFIG) --libs $(LIBS_COMMON) glibmm-2.4 giomm-2.4) -o $@ + +$(OBJ_ALL): %.o: %.cpp + $(CC) -c $(INC) $(CFLAGS) $< -o $@ + +clean: + rm -f $(OBJ_ALL) horizon-imp horizon-pool horizon-prj horizon-pool-update horizon-pool-update-parametric $(OBJ_ALL:.o=.d) + +-include $(OBJ_ALL:.o=.d) + +.PHONY: clean diff --git a/README.md b/README.md new file mode 100644 index 000000000..25db445f4 --- /dev/null +++ b/README.md @@ -0,0 +1,210 @@ +Horizon is a free EDA package. It's far from finished, but the overall +architecture is there. + +# Features for users so far +- Complete design flow from schematic entry to gerber export +- Sane library management +- Unified editor for everything from schematic symbol to board +- Netlist-aware schematic editor +- Crude obstacle-avoiding router +- Lag- and glitch-free rendering +- Undo/redo +- Copy/paste for some objects +- Builds and runs on Linux and Windows + +# Features for developers +- Written in modern C++, legacy-free codebase! +- Uses JSON as on-disk format +- Uses Gtkmm3 for GUI +- OpenGL 3 accelerated rendering +- Everything is referenced by UUIDs + + +# How do I... +## build all this +It's as easy as `make`. + +For building on Windows, use MSYS2 + +Dependencies: + +- Gtkmm3 +- libepoxy +- cairomm-pdf +- librsvg +- util-linux (on linux only) +- yaml-cpp +- sqlite + +## run +`HORIZON_POOL` needs to be set appropriately for all commands below +### horizon-imp +Symbol mode: +`horizon-imp -y ` + +Schematic mode: +`horizon-imp -c ` + +Padstack mode: +`horizon-imp -a ` + +Package mode: +`horizon-imp -k ` + +Board mode: +`horizon-imp -b ` + +### horizon-pool +Most of the -edit and -create commands will spawn $EDITOR with the file to be +edited serialized as YAML. + +``` +horizon-pool create-unit +horizon-pool edit-unit +horizon-pool create-symbol + +horizon-pool create-entity [ ...] +horizon-pool edit-entity + +#these have a GUI! +horizon-pool create-part +horizon-pool edit-part + +horizon-pool create-package +horizon-pool create-padstack +``` + +Remember to run `horizon-pool-update` after creating things + +### horizon-pool-update +``` +horizon-pool-update +``` + +Recreates the pool's SQLite database. + +### horizon-pool-update-parametric +``` +horizon-pool-update-parametric +``` + +Recreates the pool's parametric SQLite database. + +### horizon-prj +Use these to create empty blocks, schematics, etc. +``` +horizon-prj create-block + +horizon-prj create-constraints + +horizon-prj create-schematic + +horizon-prj create-board +``` + + +## get started +After having built horizon, you should setup a pool somewhere (e.g. +clone https://github.com/carrotIndustries/horizon-pool) and make the `HORIZON_POOL` environment variable point to it. Then run `horizon-pool-update` and `horizon-pool-update-parametric` to update both databasess. Since the pool is pretty empty, there's no senisble example project right now. + +Create block, constraints, schematic and board: +``` +horizon-prj create-block block.json +horizon-prj create-constraints constr.json +horizon-prj create-schematic sch.json block.json constr.json +horizon-prj create-board board.json block.json constr.json +#create empty via directory +mkdir vias + +#to edit the schematic +horizon-imp -c sch.json block.json constr.json + +#to edit the board schematic +horizon-imp -b board.json block.json constr.json vias + + +``` + +## use the interactive manipulator +To see the available key bindings, hit "?" when no tool is active. +"Space" brings up a popover showing tools that can be activated. So +far, there is no documentation on the tools itself, good luck! + + +# Theory of operation +## Pool +To explain horizon's operation it's best to start with the library +structure: Actually, there are no "libraries" in horizon. Instead, all +library data is stored in the central pool. + +At the very of bottom of the Pool, there's the unit. A unit +represents a single gate, e.g. a single amplifier of a dual operation +amplifier with its pins. + +The next level is the entity. An entity contains one or more gates that +reference units. The entity "Quad NAND gate" thus contains four gates +referencing the "NAND gate" unit and one gate referencing the "power" +unit. Entities and units are generic, so the "Two-terminal resistor" +entity can be used for any resistor of any value and package. + +To represent units on the schematic, there are symbols. Contrary to +other EDA packages, a symbol isn't the "source of truth", instead it +references a unit from which it gathers the pin names. + +Of course there are packages as well: Instead of defining each pad on +its own, one creates a padstack describing the pads shape. This +simplifies consistent packages and enables arbitrary pad shapes. + +Packages and entities are linked in parts. Apart from the references to +the entity and the package, a part maps the entities' pins to pads and +contains information such as manufacturers part number and value. To +simplify creation of similar parts, parts can inherit from another +part. A part is supposed to be something you can actually order for +easing the CAD-to-manufacturing transition. Furthermore, parts may +contain parametric data where it's appropriate for easier lookup. + +Parts, packages and entities can be tagged as the pool has no +particular hierarchy. + +Each of the objects described above lives in its own JSON file and is +identified by a UUID. A SQLite database is generated from the pool to +simplify lookup. + +The pool is supposed to be global and collaborative. + +## Interactive manipulator +Horizon's primary interface is the so-called "Interactive manipulator" +(imp). It's the unified editor for symbols, schematics, padstacks, +packages and boards. + +### Canvas +The canvas renders objects such as symbols, packages or tracks. The +output of the rendering are line segments and triangles that get +uploaded to the GPU for drawing. For rendering to to non-OpenGL +targets, the canvas provides hooks to get more information on what's +being rendered. So far, this is used by the gerber exporter and the +obstacle-avoiding router. + +### Core +Since some documents such as symbols and schematics contain the same +type of object (e.g. texts) and schematic and netlist need to be +modified in-sync, some encapsulation has to take place. The core can be +considered the glue between the document, the canvas and the tools. + +### Tools +For each action the user can do, there's a tool. Once started, a tool +receives keyboard and mouse input and modifies the document +accordingly by means of the core. When needed, a tool may bring up additional dialogs for +requesting information from the user. + +### Property editor +Low-complexity adjustments such as line with don't warrant their own +tool, that's why the the core provides a property interface. The +property editor's widgets are automatically generated from the object's +description. + +# Included third-party software + +- https://github.com/nlohmann/json/ +- http://www.angusj.com/delphi/clipper.php +- https://github.com/ivanfratric/polypartition/ diff --git a/block/block.cpp b/block/block.cpp new file mode 100644 index 000000000..61a87cbdd --- /dev/null +++ b/block/block.cpp @@ -0,0 +1,177 @@ +#include "block.hpp" +#include "json.hpp" +#include + +namespace horizon { + + Block::Block(const UUID &uu, const json &j, Pool &pool, Constraints *constr): + uuid(uu), + name(j.at("name").get()), + constraints(constr) + { + { + const json &o = j["nets"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + nets.emplace(std::make_pair(u, Net(u, it.value(), *constraints))); + } + } + { + const json &o = j["buses"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + buses.emplace(std::make_pair(u, Bus(u, it.value(), *this))); + } + } + { + const json &o = j["components"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + components.emplace(std::make_pair(u, Component(u, it.value(), pool, *this))); + } + } + } + + Block::Block(const UUID &uu): uuid(uu) {} + + Block Block::new_from_file(const std::string &filename, Pool &obj, Constraints *constr) { + json j; + std::ifstream ifs(filename); + if(!ifs.is_open()) { + throw std::runtime_error("file " +filename+ " not opened"); + } + ifs>>j; + return Block(UUID(j["uuid"].get()), j, obj, constr); + } + + Net *Block::get_net(const UUID &uu) { + return &nets.at(uu); + } + + Net *Block::insert_net() { + auto uu = UUID::random(); + auto n = &nets.emplace(uu, uu).first->second; + n->net_class = constraints->default_net_class; + return n; + } + + void Block::merge_nets(Net *net, Net *into) { + assert(net->uuid==nets.at(net->uuid).uuid); + assert(into->uuid==nets.at(into->uuid).uuid); + for(auto &it_comp: components) { + for(auto &it_conn: it_comp.second.connections) { + if(it_conn.second.net == net) { + it_conn.second.net = into; + } + } + } + nets.erase(net->uuid); + } + + void Block::update_refs() { + for(auto &it_comp: components) { + for(auto &it_conn: it_comp.second.connections) { + it_conn.second.net.update(nets); + } + } + for(auto &it_bus: buses) { + it_bus.second.update_refs(*this); + } + } + + void Block::vacuum_nets() { + std::set nets_erase; + for(const auto &it: nets) { + if(!it.second.is_power) { //don't vacuum power nets + nets_erase.emplace(it.first); + } + } + for(const auto &it: buses) { + for(const auto &it_mem : it.second.members) { + nets_erase.erase(it_mem.second.net->uuid); + } + } + for(const auto &it_comp: components) { + for(const auto &it_conn: it_comp.second.connections) { + nets_erase.erase(it_conn.second.net.uuid); + } + } + for(const auto &uu: nets_erase) { + nets.erase(uu); + } + } + + void Block::update_connection_count() { + for(auto &it: nets) { + it.second.n_pins_connected = 0; + it.second.has_bus_rippers = false; + } + for(const auto &it_comp: components) { + for(const auto &it_conn: it_comp.second.connections) { + if(it_conn.second.net) + it_conn.second.net->n_pins_connected++; + } + } + + } + + Block::Block(const Block &block): + uuid(block.uuid), + name(block.name), + nets(block.nets), + buses(block.buses), + components(block.components), + constraints(block.constraints) + { + update_refs(); + } + + void Block::operator=(const Block &block) { + uuid = block.uuid; + name = block.name; + nets = block.nets; + buses = block.buses; + components = block.components; + constraints = block.constraints; + update_refs(); + } + + json Block::serialize() { + json j; + j["name"] = name; + j["uuid"] = (std::string)uuid; + j["nets"] = json::object(); + for(const auto &it : nets) { + j["nets"][(std::string)it.first] = it.second.serialize(); + } + j["components"] = json::object(); + for(const auto &it : components) { + j["components"][(std::string)it.first] = it.second.serialize(); + } + j["buses"] = json::object(); + for(const auto &it : buses) { + j["buses"][(std::string)it.first] = it.second.serialize(); + } + + return j; + } + + Net *Block::extract_pins(const std::set> &pins, Net *net) { + if(pins.size()==0) { + return nullptr; + } + if(net == nullptr) { + net = insert_net(); + } + for(const auto &it: pins) { + Component &comp = components.at(it.at(0)); + UUIDPath<2> conn_path(it.at(1), it.at(2)); + if(comp.connections.count(conn_path)) { + //the connection may have been deleted + comp.connections.at(conn_path).net = net; + } + } + + return net; + } +} diff --git a/block/block.hpp b/block/block.hpp new file mode 100644 index 000000000..2421766dd --- /dev/null +++ b/block/block.hpp @@ -0,0 +1,45 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "pool.hpp" +#include "net.hpp" +#include "component.hpp" +#include "bus.hpp" +#include "constraints.hpp" +#include +#include +#include +#include + +namespace horizon { + using json = nlohmann::json; + + class Block:public Object { + public : + Block(const UUID &uu, const json &, Pool &pool, Constraints *constr); + Block(const UUID &uu); + static Block new_from_file(const std::string &filename, Pool &pool, Constraints *constr); + virtual Net *get_net(const UUID &uu); + UUID uuid; + std::string name; + std::map nets; + std::map buses; + std::map components; + + Block(const Block &block); + void operator=(const Block &block); + + void merge_nets(Net *net, Net *into); + void vacuum_nets(); + Net *extract_pins(const std::set> &pins, Net *net=nullptr); + void update_connection_count(); + Net *insert_net(); + + json serialize(); + + private: + void update_refs(); + Constraints *constraints = nullptr; + }; + +} diff --git a/block/bus.cpp b/block/bus.cpp new file mode 100644 index 000000000..19c5c1cf4 --- /dev/null +++ b/block/bus.cpp @@ -0,0 +1,60 @@ +#include "bus.hpp" +#include "json.hpp" +#include "block.hpp" + +namespace horizon { + + Bus::Member::Member(const UUID &uu, const json &j, Block &block): + uuid(uu), + name(j.at("name").get()), + net(&block.nets.at(j.at("net").get())) + {} + + Bus::Member::Member(const UUID &uu): uuid(uu){} + + json Bus::Member::serialize() const { + json j; + j["name"] = name; + j["net"] = (std::string)net->uuid; + return j; + } + + UUID Bus::Member::get_uuid() const { + return uuid; + } + + Bus::Bus(const UUID &uu, const json &j, Block &block): + uuid(uu), + name(j.at("name").get()) + { + { + const json &o = j["members"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + members.emplace(std::make_pair(u, Member(u, it.value(), block))); + } + } + } + + Bus::Bus(const UUID &uu): uuid(uu){}; + + void Bus::update_refs(Block &block) { + for(auto &it: members) { + it.second.net.update(block.nets); + } + } + + UUID Bus::get_uuid() const { + return uuid; + } + + json Bus::serialize() const { + json j; + j["name"] = name; + j["members"] = json::object(); + for(const auto &it : members) { + j["members"][(std::string)it.first] = it.second.serialize(); + } + return j; + } +} diff --git a/block/bus.hpp b/block/bus.hpp new file mode 100644 index 000000000..21868ff27 --- /dev/null +++ b/block/bus.hpp @@ -0,0 +1,41 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "object.hpp" +#include "unit.hpp" +#include "uuid_provider.hpp" +#include "uuid_ptr.hpp" +#include +#include +#include + +namespace horizon { + using json = nlohmann::json; + + class Bus :public UUIDProvider{ + public : + class Member: public UUIDProvider { + public: + Member(const UUID &uu, const json &, class Block &block); + Member(const UUID &uu); + UUID uuid; + std::string name; + uuid_ptr net = nullptr; + json serialize() const; + virtual UUID get_uuid() const; + + }; + + + Bus(const UUID &uu, const json &, class Block &block); + Bus(const UUID &uu); + virtual UUID get_uuid() const; + UUID uuid; + std::string name; + std::map members; + bool is_referenced = false; + void update_refs(Block &block); + json serialize() const; + }; + +} diff --git a/block/component.cpp b/block/component.cpp new file mode 100644 index 000000000..d27fd786f --- /dev/null +++ b/block/component.cpp @@ -0,0 +1,82 @@ +#include "component.hpp" +#include "json.hpp" +#include "part.hpp" + +namespace horizon { + + json Connection::serialize() const { + json j; + j["net"] = net.uuid; + return j; + } + + Connection::Connection(const json &j, Object &obj):net(obj.get_net(j.at("net").get())){ + } + + Component::Component(const UUID &uu, const json &j, Pool &pool, Object &block): + uuid(uu), + entity(pool.get_entity(j.at("entity").get())), + refdes(j.at("refdes").get()), + value(j.at("value").get()) + { + if(j.count("part")) { + part = pool.get_part(j.at("part").get()); + if(part->entity->uuid != entity->uuid) + part = nullptr; + } + + { + const json &o = j["connections"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + UUIDPath<2> u(it.key()); + if(entity->gates.count(u.at(0)) > 0) { + const auto &g = entity->gates.at(u.at(0)); + if(g.unit->pins.count(u.at(1)) > 0) { + connections.emplace(std::make_pair(u, Connection(it.value(), block))); + } + } + } + } + { + const json &o = j["pin_names"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + UUIDPath<2> u(it.key()); + if(entity->gates.count(u.at(0)) > 0) { + const auto &g = entity->gates.at(u.at(0)); + if(g.unit->pins.count(u.at(1)) > 0) { + const auto &p = g.unit->pins.at(u.at(1)); + int index = it.value(); + if((int)p.names.size() >= index+1) { + pin_names.emplace(std::make_pair(u, index)); + } + } + } + } + } + } + Component::Component(const UUID &uu):uuid(uu){} + UUID Component::get_uuid() const { + return uuid; + } + + json Component::serialize() const { + json j; + j["refdes"] = refdes; + j["value"] = value; + j["entity"] = (std::string)entity->uuid; + j["connections"] = json::object(); + if(part != nullptr) { + j["part"] = part->uuid; + } + for(const auto &it : connections) { + j["connections"][(std::string)it.first] = it.second.serialize(); + } + j["pin_names"] = json::object(); + for(const auto &it : pin_names) { + if(it.second != -1) { + j["pin_names"][(std::string)it.first] = it.second; + } + } + return j; + } +} diff --git a/block/component.hpp b/block/component.hpp new file mode 100644 index 000000000..576a8b896 --- /dev/null +++ b/block/component.hpp @@ -0,0 +1,56 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "common.hpp" +#include "unit.hpp" +#include "junction.hpp" +#include "line.hpp" +#include "arc.hpp" +#include "object.hpp" +#include "entity.hpp" +#include "uuid_path.hpp" +#include "uuid_ptr.hpp" +#include "net.hpp" +#include "pool.hpp" +#include +#include +#include + +namespace horizon { + using json = nlohmann::json; + + class Connection { + public: + Connection(const json &j, Object &obj); + Connection(Net *n):net(n){} + uuid_ptr net; + + json serialize() const; + + }; + + + class Component : public Object, public UUIDProvider { + public : + Component(const UUID &uu, const json &j, Pool &pool, Object &block); + Component(const UUID &uu); + + + virtual UUID get_uuid() const ; + + UUID uuid; + Entity *entity; + class Part *part = nullptr; + std::string refdes; + std::string value; + std::map, Connection> connections; + std::map, int> pin_names; + + json serialize() const; + virtual ~Component() {} + + + private : + //void update_refs(); + }; +} diff --git a/block/net.cpp b/block/net.cpp new file mode 100644 index 000000000..35bb006d8 --- /dev/null +++ b/block/net.cpp @@ -0,0 +1,36 @@ +#include "net.hpp" +#include "json.hpp" + +namespace horizon { + + Net::Net(const UUID &uu, const json &j, const Constraints &constr): + uuid(uu), + name(j.at("name").get()), + is_power(j.value("is_power", false)) + { + if(j.count("net_class")) { + net_class = &constr.net_classes.at(j.at("net_class").get()); + } + else { + net_class = constr.default_net_class; + } + } + + Net::Net (const UUID &uu): uuid(uu){}; + + UUID Net::get_uuid() const { + return uuid; + } + + json Net::serialize() const { + json j; + j["name"] = name; + j["is_power"] = is_power; + j["net_class"] = net_class->uuid; + return j; + } + + bool Net::is_named() const { + return name.size()>0; + } +} diff --git a/block/net.hpp b/block/net.hpp new file mode 100644 index 000000000..04fec437a --- /dev/null +++ b/block/net.hpp @@ -0,0 +1,34 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "object.hpp" +#include "unit.hpp" +#include "uuid_provider.hpp" +#include "constraints.hpp" +#include +#include +#include + +namespace horizon { + using json = nlohmann::json; + + class Net :public UUIDProvider{ + public : + Net(const UUID &uu, const json &, const Constraints &constr); + Net(const UUID &uu); + virtual UUID get_uuid() const; + UUID uuid; + std::string name; + bool is_power = false; + const NetClass *net_class = nullptr; + + //not saved + bool is_power_forced = false; + bool is_bussed = false; + unsigned int n_pins_connected = 0; + bool has_bus_rippers = false; + json serialize() const; + bool is_named() const; + }; + +} diff --git a/board/board.cpp b/board/board.cpp new file mode 100644 index 000000000..3045d012a --- /dev/null +++ b/board/board.cpp @@ -0,0 +1,563 @@ +#include "board.hpp" +#include "json.hpp" +#include "part.hpp" + +namespace horizon { + + Board::Board(const UUID &uu, const json &j, Block &iblock, Pool &pool, ViaPadstackProvider &vpp): + uuid(uu), + block(&iblock), + name(j.at("name").get()), + n_inner_layers(j.value("n_inner_layers", 0)) + { + if(j.count("polygons")) { + const json &o = j["polygons"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + polygons.emplace(std::make_pair(u, Polygon(u, it.value()))); + } + } + if(j.count("holes")) { + const json &o = j["holes"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + holes.emplace(std::make_pair(u, Hole(u, it.value()))); + } + } + if(j.count("packages")) { + const json &o = j["packages"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + UUID comp_uuid (it.value().at("component").get()); + if(block->components.count(comp_uuid) && block->components.at(comp_uuid).part != nullptr) { + auto u = UUID(it.key()); + packages.emplace(std::make_pair(u, BoardPackage(u, it.value(), *block, pool))); + } + } + } + if(j.count("junctions")) { + const json &o = j["junctions"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + junctions.emplace(std::make_pair(u, Junction(u, it.value()))); + } + } + if(j.count("tracks")) { + const json &o = j["tracks"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + bool valid = true; + for(const auto &it_ft: {it.value().at("from"), it.value().at("to")}) { + if(it_ft.at("pad") != nullptr) { + UUIDPath<2> path(it_ft.at("pad").get()); + valid = packages.count(path.at(0)); + if(valid) { + auto &pkg = packages.at(path.at(0)); + if(pkg.component->part->pad_map.count(path.at(1))==0) { + valid = false; + } + } + } + } + if(valid) { + auto u = UUID(it.key()); + tracks.emplace(std::make_pair(u, Track(u, it.value(), *this))); + } + } + } + if(j.count("vias")) { + const json &o = j["vias"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + vias.emplace(std::make_pair(u, Via(u, it.value(), *this, vpp))); + } + } + if(j.count("texts")) { + const json &o = j["texts"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + texts.emplace(std::make_pair(u, Text(u, it.value()))); + } + } + } + + Board Board::new_from_file(const std::string &filename, Block &block, Pool &pool, ViaPadstackProvider &vpp) { + json j; + std::ifstream ifs(filename); + if(!ifs.is_open()) { + throw std::runtime_error("file " +filename+ " not opened"); + } + ifs>>j; + ifs.close(); + return Board(UUID(j["uuid"].get()), j, block, pool, vpp); + } + + Board::Board(const UUID &uu, Block &bl): uuid(uu), block(&bl) { + + } + + Board::Board(const Board &brd): + uuid(brd.uuid), + block(brd.block), + name(brd.name), + n_inner_layers(brd.n_inner_layers), + polygons(brd.polygons), + holes(brd.holes), + packages(brd.packages), + junctions(brd.junctions), + tracks(brd.tracks), + airwires(brd.airwires), + vias(brd.vias), + texts(brd.texts), + warnings(brd.warnings) + { + update_refs(); + } + + void Board::operator=(const Board &brd) { + uuid = brd.uuid; + block = brd.block; + name = brd.name; + n_inner_layers = brd.n_inner_layers; + polygons = brd.polygons; + holes = brd.holes; + packages.clear(); + packages = brd.packages; + junctions = brd.junctions; + tracks = brd.tracks; + airwires = brd.airwires; + vias = brd.vias; + texts = brd.texts; + warnings = brd.warnings; + update_refs(); + } + + void Board::update_refs() { + for(auto &it: packages) { + it.second.component.update(block->components); + for(auto &it_pad: it.second.package.pads) { + it_pad.second.net.update(block->nets); + } + } + for(auto &it: tracks) { + it.second.update_refs(*this); + } + for(auto &it: airwires) { + it.second.update_refs(*this); + } + for(auto &it: net_segments) { + it.second.update(block->nets); + } + for(auto &it: vias) { + it.second.junction.update(junctions); + } + for(auto &it: junctions) { + it.second.net.update(block->nets); + } + } + + static void flip_package_layer(int &layer) { + if(layer == -1) + return; + layer = -layer-100; + } + + + bool Board::propagate_net_segments() { + net_segments.clear(); + net_segments.emplace(UUID(), nullptr); + + bool run = true; + while(run) { + run = false; + for(auto &it_pkg: packages) { + for(auto &it_pad: it_pkg.second.package.pads) { + if(!it_pad.second.net_segment && it_pad.second.net) { + it_pad.second.net_segment = UUID::random(); + net_segments.emplace(it_pad.second.net_segment, it_pad.second.net); + run = true; + break; + } + } + if(run) + break; + } + if(run == false) { + break; + } + unsigned int n_assigned = 1; + while(n_assigned) { + n_assigned = 0; + for(auto &it: tracks) { + if(it.second.net_segment) { //track with net seg + for(auto &it_ft: {it.second.from, it.second.to}) { + if(it_ft.is_junc() && !it_ft.junc->net_segment) { + it_ft.junc->net_segment = it.second.net_segment; + n_assigned++; + } + else if(it_ft.is_pad() && !it_ft.pad->net_segment) { + it_ft.pad->net_segment = it.second.net_segment; + n_assigned++; + } + } + } + else { //track without net seg + for(auto &it_ft: {it.second.from, it.second.to}) { + if(it_ft.is_junc() && it_ft.junc->net_segment) { + it.second.net_segment = it_ft.junc->net_segment; + n_assigned++; + } + else if(it_ft.is_pad() && it_ft.pad->net_segment) { + it.second.net_segment = it_ft.pad->net_segment; + //it_ft.pin->connected_net_lines.emplace(it.first, &it.second); + n_assigned++; + } + } + } + } + } + } + for(auto &it: junctions) { + it.second.net = net_segments.at(it.second.net_segment); + } + + bool done = true; + for (auto it = tracks.begin(); it != tracks.end();) { + it->second.net = net_segments.at(it->second.net_segment); + bool erased = false; + for(auto &it_ft: {it->second.from, it->second.to}) { + if(it_ft.is_pad() && it_ft.pad->net) { + if(it->second.net != it_ft.pad->net) { + done = false; + tracks.erase(it++); + erased = true; + break; + } + } + } + if(!erased) + it++; + } + return done; + } + + void Board::update_airwires() { + std::map> nets_with_more_than_one_net_segment; + + + for(auto &it_pkg: packages) { + for(auto &it_pad: it_pkg.second.package.pads) { + if(it_pad.second.net == nullptr) + continue; + if(!nets_with_more_than_one_net_segment.count(it_pad.second.net)) { + nets_with_more_than_one_net_segment[it_pad.second.net]; + } + nets_with_more_than_one_net_segment.at(it_pad.second.net).insert(it_pad.second.net_segment); + } + } + + for (auto it = nets_with_more_than_one_net_segment.cbegin(); it != nets_with_more_than_one_net_segment.cend();) { + if (it->second.size()<2) { + nets_with_more_than_one_net_segment.erase(it++); + } + else { + it++; + } + } + + airwires.clear(); + for(const auto &it: nets_with_more_than_one_net_segment) { + std::cout<< "need air wire " << it.first->name << " " << it.second.size() << std::endl; + std::map> ns_points; + for(const auto &it_ns: it.second) { + ns_points[it_ns]; + for(auto &it_junc: junctions) { + if(it_junc.second.net_segment == it_ns) { + ns_points.at(it_ns).emplace_back(&it_junc.second); + } + } + for(auto &it_pkg: packages) { + for(auto &it_pad: it_pkg.second.package.pads) { + if(it_pad.second.net_segment == it_ns) { + ns_points.at(it_ns).emplace_back(&it_pkg.second, &it_pad.second); + } + } + } + } + //std::set> ns_connected; + std::map ns_map; + for(const auto &it_ns: ns_points) { + ns_map.emplace(it_ns.first, it_ns.first); + } + for(const auto &it_ns: ns_points) { //for each net segment + //if(ns_connected.count(it_ns.first)) + // continue; + const Track::Connection *pt_a = nullptr; + const Track::Connection *pt_b = nullptr; + UUID ns_other; + uint64_t dist_nearest = UINT64_MAX; + for(const auto &pt: it_ns.second) { //for each point + auto pos = pt.get_position(); + for(const auto &it_ns_other: ns_points) { + if(ns_map.at(it_ns_other.first) == ns_map.at(it_ns.first)) + continue; + for(const auto &pt_other: it_ns_other.second) { + auto pos_other = pt_other.get_position(); + uint64_t dist = (pos_other-pos).mag_sq(); + if(dist < dist_nearest) { + dist_nearest = dist; + pt_a = &pt; + pt_b = &pt_other; + ns_other = it_ns_other.first; + } + } + } + } + if(ns_other) { + //if(ns_map.at(ns_other) != ns_map.at(it_ns.first)){ + if(true) { + assert(pt_a); + assert(pt_b); + std::cout << "add aw " << (std::string) it_ns.first << " " << (std::string)ns_other << std::endl; + std::cout << "add am " << (std::string) ns_map.at(it_ns.first) << " " << (std::string)ns_map.at(ns_other) << std::endl; + //merge net segs + for(auto &it_map: ns_map) { + if(it_map.second == ns_map.at(it_ns.first)) { + it_map.second = ns_map.at(ns_other); + } + } + std::cout << "ns map" << std::endl; + for(const auto &it_map: ns_map) { + std::cout<< (std::string)it_map.first << " " << (std::string)it_map.second << std::endl; + } + + std::cout << "--" << std::endl; + auto uu = UUID::random(); + auto &aw = airwires.emplace(uu, uu).first->second; + aw.from = *pt_a; + aw.to = *pt_b; + aw.is_air = true; + } + } + + } + } + std::cout << "have airwires " << airwires.size() << std::endl; + } + + void Board::vacuum_junctions() { + for(auto it = junctions.begin(); it != junctions.end();) { + if(it->second.connection_count == 0 && it->second.has_via==false) { + it = junctions.erase(it); + } + else { + it++; + } + } + } + + void Board::expand(bool careful) { + delete_dependants(); + warnings.clear(); + + + for(auto &it: junctions) { + it.second.temp = false; + it.second.layer = 10000; + it.second.has_via = false; + it.second.needs_via = false; + it.second.connection_count = 0; + } + + for(const auto &it: tracks) { + for(const auto &it_ft: {it.second.from, it.second.to}) { + if(it_ft.is_junc()) { + auto ju = it_ft.junc; + ju->connection_count ++; + if(ju->layer == 10000) { //none assigned + ju->layer = it.second.layer; + } + else if(ju->layer == 10001) {//invalid + //nop + } + else if(ju->layer != it.second.layer) { + ju->layer = 10001; + ju->needs_via = true; + } + } + } + if(it.second.from.get_position() == it.second.to.get_position()) { + warnings.emplace_back(it.second.from.get_position(), "Zero length track"); + } + } + + for(auto &it: vias) { + it.second.junction->has_via = true; + it.second.padstack = *it.second.vpp_padstack; + it.second.padstack.expand_inner(n_inner_layers); + } + + for(const auto &it: junctions) { + if(it.second.needs_via && !it.second.has_via) { + warnings.emplace_back(it.second.position, "Junction needs via"); + } + } + + vacuum_junctions(); + + for(auto &it: packages) { + it.second.pool_package = it.second.component->part->package; + it.second.package = *it.second.pool_package; + it.second.placement.mirror = it.second.flip; + for(auto &it2: it.second.package.pads) { + it2.second.padstack.expand_inner(n_inner_layers); + } + + if(it.second.flip) { + for(auto &it2: it.second.package.lines) { + flip_package_layer(it2.second.layer); + } + for(auto &it2: it.second.package.arcs) { + flip_package_layer(it2.second.layer); + } + for(auto &it2: it.second.package.texts) { + flip_package_layer(it2.second.layer); + switch(it2.second.orientation) { + case Orientation::RIGHT : + it2.second.orientation = Orientation::LEFT; + break; + case Orientation::LEFT : + it2.second.orientation = Orientation::RIGHT; + break; + case Orientation::DOWN : + it2.second.orientation = Orientation::UP; + break; + case Orientation::UP: + it2.second.orientation = Orientation::DOWN; + break; + } + } + for(auto &it2: it.second.package.polygons) { + flip_package_layer(it2.second.layer); + } + for(auto &it2: it.second.package.pads) { + for(auto &it3: it2.second.padstack.polygons) { + flip_package_layer(it3.second.layer); + } + } + } + + for(auto &it_text: it.second.package.texts) { + if((it_text.second.text == "$REFDES") || (it_text.second.text == "$RD")) { + it_text.second.text = it.second.component->refdes; + } + } + } + + + for(auto &it: packages) { + for(auto &it_pad: it.second.package.pads) { + const auto &pad_map_item = it.second.component->part->pad_map.at(it_pad.first); + auto pin_path = UUIDPath<2>(pad_map_item.gate->uuid, pad_map_item.pin->uuid); + if(it.second.component->connections.count(pin_path)) { + const auto &conn = it.second.component->connections.at(pin_path); + it_pad.second.net = conn.net; + } + else { + it_pad.second.net = nullptr; + } + } + } + + do { + for(auto &it: packages) { + for(auto &it_pad: it.second.package.pads) { + it_pad.second.net_segment = UUID(); + } + } + for(auto &it: tracks) { + it.second.update_refs(*this); + it.second.net = nullptr; + it.second.net_segment = UUID(); + } + for(auto &it: junctions) { + it.second.net = nullptr; + it.second.net_segment = UUID(); + } + } while(propagate_net_segments() == false); + update_airwires(); + } + + void Board::disconnect_package(BoardPackage *pkg) { + std::map pad_junctions; + for(auto &it_track: tracks) { + Track *tr = &it_track.second; + //if((line->to_symbol && line->to_symbol->uuid == it.uuid) || (line->from_symbol &&line->from_symbol->uuid == it.uuid)) { + for(auto it_ft: {&tr->to, &tr->from}) { + if(it_ft->package == pkg) { + Junction *j = nullptr; + if(pad_junctions.count(it_ft->pad)) { + j = pad_junctions.at(it_ft->pad); + } + else { + auto uu = UUID::random(); + auto x = pad_junctions.emplace(it_ft->pad, &junctions.emplace(uu, uu).first->second); + j = x.first->second; + } + auto c = it_ft->get_position(); + j->position = c; + it_ft->connect(j); + } + } + } + } + + void Board::delete_dependants() { + auto via_it = vias.begin(); + while(via_it != vias.end()) { + if(junctions.count(via_it->second.junction.uuid) == 0) { + via_it = vias.erase(via_it); + } + else { + via_it++; + } + } + } + + + json Board::serialize() const { + json j; + j["type"] = "board"; + j["uuid"] = (std::string)uuid; + j["block"] = (std::string)block->uuid; + j["name"] = name; + j["n_inner_layers"] = n_inner_layers; + j["polygons"] = json::object(); + for(const auto &it: polygons) { + j["polygons"][(std::string)it.first] = it.second.serialize(); + } + j["holes"] = json::object(); + for(const auto &it: holes) { + j["holes"][(std::string)it.first] = it.second.serialize(); + } + j["packages"] = json::object(); + for(const auto &it: packages) { + j["packages"][(std::string)it.first] = it.second.serialize(); + } + j["junctions"] = json::object(); + for(const auto &it: junctions) { + j["junctions"][(std::string)it.first] = it.second.serialize(); + } + j["tracks"] = json::object(); + for(const auto &it: tracks) { + j["tracks"][(std::string)it.first] = it.second.serialize(); + } + j["vias"] = json::object(); + for(const auto &it: vias) { + j["vias"][(std::string)it.first] = it.second.serialize(); + } + j["texts"] = json::object(); + for(const auto &it: texts) { + j["texts"][(std::string)it.first] = it.second.serialize(); + } + return j; + } +} diff --git a/board/board.hpp b/board/board.hpp new file mode 100644 index 000000000..48f303f68 --- /dev/null +++ b/board/board.hpp @@ -0,0 +1,65 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "pool.hpp" +#include "block.hpp" +#include "polygon.hpp" +#include "hole.hpp" +#include "board_package.hpp" +#include "junction.hpp" +#include "track.hpp" +#include "via_padstack_provider.hpp" +#include "via.hpp" +#include "clipper/clipper.hpp" +#include "warning.hpp" +#include +#include +#include + +namespace horizon { + using json = nlohmann::json; + + class Board { + private : + Board(const UUID &uu, const json &, Block &block, Pool &pool, ViaPadstackProvider &vpp); + //unsigned int update_nets(); + bool propagate_net_segments(); + std::map> net_segments; + + + public : + static Board new_from_file(const std::string &filename, Block &block, Pool &pool, ViaPadstackProvider &vpp); + Board(const UUID &uu, Block &block); + + void expand(bool careful=false); + + Board(const Board &brd); + void operator=(const Board &brd); + void update_refs(); + void update_airwires(); + void disconnect_package(BoardPackage *pkg); + void delete_dependants(); + void vacuum_junctions(); + + UUID uuid; + Block *block; + std::string name; + unsigned int n_inner_layers = 0; + std::map polygons; + std::map holes; + std::map packages; + std::map junctions; + std::map tracks; + std::map airwires; + std::map vias; + std::map texts; + + std::vector warnings; + + ClipperLib::Paths obstacles; + ClipperLib::Path track_path; + + json serialize() const; + }; + +} diff --git a/board/board_package.cpp b/board/board_package.cpp new file mode 100644 index 000000000..6102302ad --- /dev/null +++ b/board/board_package.cpp @@ -0,0 +1,31 @@ +#include "board_package.hpp" +#include "json.hpp" +#include "part.hpp" + +namespace horizon { + + BoardPackage::BoardPackage(const UUID &uu, const json &j, Block &block, Pool &pool): + uuid(uu), + component(&block.components.at(j.at("component").get())), + pool_package(component->part->package), + package(*pool_package), + placement(j.at("placement")), + flip(j.at("flip").get()) + { + } + BoardPackage::BoardPackage(const UUID &uu, Component *comp): uuid(uu), component(comp), pool_package(component->part->package), package(*pool_package) {} + + json BoardPackage::serialize() const { + json j; + j["component"] = (std::string)component.uuid; + j["placement"] = placement.serialize(); + j["flip"] = flip; + + + return j; + } + + UUID BoardPackage::get_uuid() const { + return uuid; + } +} diff --git a/board/board_package.hpp b/board/board_package.hpp new file mode 100644 index 000000000..5cf0d3b3c --- /dev/null +++ b/board/board_package.hpp @@ -0,0 +1,35 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "object.hpp" +#include "component.hpp" +#include "package.hpp" +#include "uuid_ptr.hpp" +#include "placement.hpp" +#include "uuid_provider.hpp" +#include "pool.hpp" +#include +#include +#include + +namespace horizon { + using json = nlohmann::json; + + class BoardPackage: public UUIDProvider { + public : + BoardPackage(const UUID &uu, const json &, Block &block, Pool &pool); + BoardPackage(const UUID &uu, Component *comp); + UUID uuid; + uuid_ptr component; + Package *pool_package; + Package package; + + Placement placement; + bool flip = false; + std::vector> texts; + + UUID get_uuid() const override; + json serialize() const; + }; + +} diff --git a/board/track.cpp b/board/track.cpp new file mode 100644 index 000000000..4e08dca11 --- /dev/null +++ b/board/track.cpp @@ -0,0 +1,164 @@ +#include "track.hpp" +#include "lut.hpp" +#include "json.hpp" +#include "object.hpp" +#include "board_package.hpp" +#include "board.hpp" + +namespace horizon { + + Track::Connection::Connection(const json &j, Board &brd) { + if(!j.at("junc").is_null()) { + junc = &brd.junctions.at(j.at("junc").get()); + } + else if(!j.at("pad").is_null()) { + UUIDPath<2> pad_path(j.at("pad").get()); + package = &brd.packages.at(pad_path.at(0)); + pad = &package->package.pads.at(pad_path.at(1)); + } + else { + assert(false); + } + } + + Track::Connection::Connection(Junction *j) { + connect(j); + } + Track::Connection::Connection(BoardPackage *pkg, Pad *pa) { + connect(pkg, pa); + } + + UUIDPath<2 >Track::Connection::get_pad_path() const { + assert(junc==nullptr); + return UUIDPath<2>(package->uuid, pad->uuid); + } + + bool Track::Connection::is_junc() const { + if(junc) { + assert(!is_pad()); + return true; + } + return false; + } + + bool Track::Connection::is_pad() const { + if(package) { + assert(pad); + assert(!is_junc()); + return true; + } + return false; + } + + void Track::Connection::connect(Junction *j) { + junc = j; + package = nullptr; + pad = nullptr; + } + + void Track::Connection::connect(BoardPackage *pkg, Pad *pa) { + junc = nullptr; + package = pkg; + pad = pa; + } + + void Track::Connection::update_refs(Board &brd) { + junc.update(brd.junctions); + package.update(brd.packages); + if(package) + pad.update(package->package.pads); + } + + Coordi Track::Connection::get_position() const { + if(is_junc()) { + return junc->position; + } + else if(is_pad()) { + auto tr = package->placement; + if(package->flip) + tr.invert_angle(); + return tr.transform(pad->placement.shift); + } + else { + assert(false); + } + } + + json Track::Connection::serialize() const { + json j; + j["junc"] = nullptr; + j["pad"] = nullptr; + if(is_junc()) { + j["junc"] = (std::string)junc->uuid; + } + else if(is_pad()) { + j["pad"] = (std::string)get_pad_path(); + } + else { + assert(false); + } + return j; + } + + UUID Track::Connection::get_net_segment() const { + if(is_junc()) { + return junc->net_segment; + } + else if(is_pad()) { + return UUID(); + //return pad->net_segment; + } + else { + assert(false); + return UUID(); + } + } + + + Track::Track(const UUID &uu, const json &j, Board &brd): + uuid(uu), + layer(j.value("layer", 0)), + width(j.value("width", 0)), + width_from_net_class(j.value("width_from_net_class", true)), + from(j["from"], brd), + to(j["to"], brd) + { + + } + + void Track::update_refs(Board &brd) { + to.update_refs(brd); + from.update_refs(brd); + net.update(brd.block->nets); + } + + UUID Track::get_uuid() const { + return uuid; + } + + Track::Track(const UUID &uu): uuid(uu) {} + + json Track::serialize() const { + json j; + j["from"] = from.serialize(); + j["to"] = to.serialize(); + j["layer"] = layer; + j["width"] = width; + j["width_from_net_class"] = width_from_net_class; + + return j; + } + + bool Track::coord_on_line(const Coordi &p) const { + Coordi a = Coordi::min(from.get_position(), to.get_position()); + Coordi b = Coordi::max(from.get_position(), to.get_position()); + if(p.x >= a.x && p.x <= b.x && p.y >= a.y && p.y <= b.y) { //inside bbox + auto c = to.get_position()-from.get_position(); + auto d = p-from.get_position(); + if((c.dot(d))*(c.dot(d)) == c.mag_sq()*d.mag_sq()) { + return true; + } + } + return false; + } +} diff --git a/board/track.hpp b/board/track.hpp new file mode 100644 index 000000000..4b4e79187 --- /dev/null +++ b/board/track.hpp @@ -0,0 +1,68 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "common.hpp" +#include "junction.hpp" +#include "object.hpp" +#include "uuid_provider.hpp" +#include "uuid_ptr.hpp" +#include "board_package.hpp" +#include "net.hpp" +#include +#include +#include + + +namespace horizon { + using json = nlohmann::json; + + + class Track : public UUIDProvider{ + public : + enum class End {TO, FROM}; + + Track(const UUID &uu, const json &j, class Board &brd); + Track(const UUID &uu); + + void update_refs(class Board &brd); + virtual UUID get_uuid() const; + bool coord_on_line(const Coordi &coord) const; + + UUID uuid; + uuid_ptr net=nullptr; + UUID net_segment = UUID(); + int layer = 0; + uint64_t width = 0; + bool width_from_net_class = true; + bool is_air = false; + + + + class Connection { + public: + Connection() {} + Connection(const json &j, Board &brd); + Connection(Junction *j); + Connection(BoardPackage *pkg, Pad *pad); + uuid_ptr junc = nullptr; + uuid_ptr package = nullptr; + uuid_ptr pad = nullptr; + + void connect(Junction *j); + void connect(BoardPackage *pkg, Pad *pad); + UUIDPath<2> get_pad_path() const ; + bool is_junc() const ; + bool is_pad() const ; + UUID get_net_segment() const; + void update_refs(class Board &brd); + Coordi get_position() const; + json serialize() const; + }; + + Connection from; + Connection to; + + + json serialize() const; + }; +} diff --git a/board/via.cpp b/board/via.cpp new file mode 100644 index 000000000..727b5b763 --- /dev/null +++ b/board/via.cpp @@ -0,0 +1,24 @@ +#include "via.hpp" +#include "board.hpp" +#include "json.hpp" +#include "via_padstack_provider.hpp" + +namespace horizon { + + Via::Via(const UUID &uu, const json &j, Board &brd, ViaPadstackProvider &vpp): + uuid(uu), + junction(&brd.junctions.at(j.at("junction").get())), + vpp_padstack(vpp.get_padstack(j.at("padstack").get())), + padstack(*vpp_padstack) + { + } + + Via::Via(const UUID &uu, Padstack *ps): uuid(uu), vpp_padstack(ps), padstack(*vpp_padstack) {} + + json Via::serialize() const { + json j; + j["junction"] = (std::string)junction->uuid; + j["padstack"] = (std::string)vpp_padstack->uuid; + return j; + } +} diff --git a/board/via.hpp b/board/via.hpp new file mode 100644 index 000000000..2bc2debd1 --- /dev/null +++ b/board/via.hpp @@ -0,0 +1,31 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "common.hpp" +#include "uuid_ptr.hpp" +#include "junction.hpp" +#include "padstack.hpp" +#include +#include +#include +#include + + +namespace horizon { + using json = nlohmann::json; + + class Via { + public : + Via(const UUID &uu, const json &j, class Board &brd, class ViaPadstackProvider &vpp); + Via(const UUID &uu, Padstack *ps); + + UUID uuid; + + uuid_ptr junction = nullptr; + Padstack *vpp_padstack = nullptr; + Padstack padstack; + + + json serialize() const; + }; +} diff --git a/board/via_padstack_provider.cpp b/board/via_padstack_provider.cpp new file mode 100644 index 000000000..59b173069 --- /dev/null +++ b/board/via_padstack_provider.cpp @@ -0,0 +1,41 @@ +#include "via_padstack_provider.hpp" +#include +#include "json.hpp" + +namespace horizon { + ViaPadstackProvider::ViaPadstackProvider(const std::string &bp):base_path(bp) { + update_available(); + } + + void ViaPadstackProvider::update_available() { + Glib::Dir dir(base_path); + padstacks_available.clear(); + for(const auto &it: dir) { + std::string filename = base_path + "/" +it; + json j; + std::ifstream ifs(filename); + if(!ifs.is_open()) { + throw std::runtime_error("file " +filename+ " not opened"); + } + ifs>>j; + ifs.close(); + padstacks_available.emplace(UUID(j.at("uuid").get()), ViaPadstackProvider::PadstackEntry(filename, j.at("name"))); + } + } + + const std::map &ViaPadstackProvider::get_padstacks_available() const { + return padstacks_available; + } + + Padstack *ViaPadstackProvider::get_padstack(const UUID &uu) { + if(padstacks.count(uu)) { + return &padstacks.at(uu); + } + if(padstacks_available.count(uu)) { + padstacks.emplace(uu, Padstack::new_from_file(padstacks_available.at(uu).path)); + return &padstacks.at(uu); + } + return nullptr; + } + +} diff --git a/board/via_padstack_provider.hpp b/board/via_padstack_provider.hpp new file mode 100644 index 000000000..1660d094a --- /dev/null +++ b/board/via_padstack_provider.hpp @@ -0,0 +1,41 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "common.hpp" +#include "unit.hpp" +#include "entity.hpp" +#include "object.hpp" +#include "symbol.hpp" +#include "padstack.hpp" +#include +#include +#include +#include + +namespace horizon { + + class ViaPadstackProvider { + public : + ViaPadstackProvider(const std::string &p); + Padstack *get_padstack(const UUID &uu); + void update_available(); + class PadstackEntry{ + public: + PadstackEntry(const std::string &p, const std::string &n): + path(p), name(n) + {} + + std::string path; + std::string name; + }; + const std::map &get_padstacks_available() const; + + private : + std::string base_path; + std::map padstacks; + std::map padstacks_available; + + + }; + +} diff --git a/canvas/box_selection.cpp b/canvas/box_selection.cpp new file mode 100644 index 000000000..adc9ca807 --- /dev/null +++ b/canvas/box_selection.cpp @@ -0,0 +1,193 @@ +#include "box_selection.hpp" +#include "gl_util.hpp" +#include "canvas.hpp" + +namespace horizon { + BoxSelection::BoxSelection(class CanvasGL *c): ca(c), active(0) { + } + + + static GLuint create_vao (GLuint program) { + GLuint vao, buffer; + + /* we need to create a VAO to store the other buffers */ + glGenVertexArrays (1, &vao); + glBindVertexArray (vao); + + /* this is the VBO that holds the vertex data */ + glGenBuffers (1, &buffer); + glBindBuffer (GL_ARRAY_BUFFER, buffer); + //data is buffered lateron + + + static const GLfloat vertices[] = {0}; + glBufferData (GL_ARRAY_BUFFER, sizeof (vertices), vertices, GL_STATIC_DRAW); + + /* enable and set the color attribute */ + /* reset the state; we will re-enable the VAO when needed */ + glBindBuffer (GL_ARRAY_BUFFER, 0); + glBindVertexArray (0); + + glDeleteBuffers (1, &buffer); + return vao; + } + + void BoxSelection::realize() { + program = gl_create_program_from_resource("/net/carrotIndustries/horizon/canvas/shaders/selection-vertex.glsl", "/net/carrotIndustries/horizon/canvas/shaders/selection-fragment.glsl", nullptr); + vao = create_vao(program); + + GET_LOC(this, screenmat); + GET_LOC(this, scale); + GET_LOC(this, offset); + GET_LOC(this, a); + GET_LOC(this, b); + } + + void BoxSelection::render() { + if(active != 2) { + return; + } + glUseProgram(program); + glBindVertexArray (vao); + glUniformMatrix3fv(screenmat_loc, 1, GL_TRUE, ca->screenmat.data()); + glUniform1f(scale_loc, ca->scale); + glUniform2f(offset_loc, ca->offset.x, ca->offset.y); + glUniform2f(a_loc, sel_a.x, sel_a.y); + glUniform2f(b_loc, sel_b.x, sel_b.y); + glEnable (GL_BLEND); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glDrawArrays (GL_TRIANGLE_STRIP, 0, 4); + glDisable(GL_BLEND); + + glBindVertexArray (0); + glUseProgram (0); + } + + void BoxSelection::drag_begin(GdkEventButton *button_event) { + if(!ca->selection_allowed) + return; + gdouble x,y; + gdk_event_get_coords((GdkEvent*)button_event, &x, &y); + if(button_event->button==1) { //inside of grid and middle mouse button + active = 1; + sel_o = Coordf(x,y); + sel_a = ca->screen2canvas(sel_o); + sel_b = sel_a; + ca->queue_draw(); + } + } + + void BoxSelection::drag_move(GdkEventMotion *motion_event) { + gdouble x,y; + gdk_event_get_coords((GdkEvent*)motion_event, &x, &y); + if(active==1) { + if(ABS(sel_o.x - x) > 10 &&ABS(sel_o.y - y) > 10) { + active = 2; + ca->selection_mode = CanvasGL::SelectionMode::NORMAL; + } + } + if(active==2) { + sel_b = ca->screen2canvas(Coordf(x,y)); + update(); + ca->queue_draw(); + } + } + + void BoxSelection::drag_end(GdkEventButton *button_event) { + if(button_event->button==1) { //inside of grid and middle mouse button { + if(active==2) { + bool add = button_event->state & Gdk::SHIFT_MASK; + for(auto &it: ca->selectables.items) { + if(it.flags & 2) { + it.flags |= 1; + } + else { + if(!add) + it.flags &= ~1; + } + it.flags &= ~2; + } + ca->request_push(); + ca->s_signal_selection_changed.emit(); + } + else if(active == 1) { + std::cout << "click select" << std::endl; + if(ca->selection_mode == CanvasGL::SelectionMode::HOVER) { //just select what was selecte by hover select + ca->selection_mode = CanvasGL::SelectionMode::NORMAL; + ca->s_signal_selection_changed.emit(); + } + else { + gdouble x,y; + gdk_event_get_coords((GdkEvent*)button_event, &x, &y); + auto c = ca->screen2canvas({(float)x,(float)y}); + std::vector in_selection; + unsigned int i = 0; + for(auto &it: ca->selectables.items) { + it.flags=0 ; + if(it.inside(c, 10/ca->scale) && ca->selection_filter.can_select(ca->selectables.items_ref[i])) { + in_selection.push_back(i); + } + i++; + } + if(in_selection.size()>1) { + ca->set_selection({}, false); + for(const auto it: ca->clarify_menu->get_children()) { + ca->clarify_menu->remove(*it); + } + for(auto i: in_selection) { + const auto sr = ca->selectables.items_ref[i]; + + auto text = object_descriptions.at(sr.type).name; + auto la = Gtk::manage(new Gtk::MenuItem(text)); + la->signal_select().connect([this, sr] { + ca->set_selection({sr}, false); + }); + la->signal_deselect().connect([this] { + ca->set_selection({}, false); + }); + la->signal_activate().connect([this, sr] { + ca->set_selection({sr}, true); + }); + la->show(); + ca->clarify_menu->append(*la); + } + ca->clarify_menu->popup_at_pointer((GdkEvent*)button_event); + } + else if(in_selection.size()==1){ + ca->set_selection({ca->selectables.items_ref[in_selection.front()]}); + } + else if(in_selection.size()==0){ + ca->set_selection({}); + } + + + + } + + + } + active = 0; + ca->queue_draw(); + } + } + + void BoxSelection::update() { + float xmin = std::min(sel_a.x, sel_b.x); + float xmax = std::max(sel_a.x, sel_b.x); + float ymin = std::min(sel_a.y, sel_b.y); + float ymax = std::max(sel_a.y, sel_b.y); + unsigned int i = 0; + for(auto &it: ca->selectables.items) { + it.flags &= ~2; + if(it.x > xmin && it.x < xmax && it.y > ymin && it.y < ymax) { + if(ca->selection_filter.can_select(ca->selectables.items_ref[i])) + it.flags |= 2; + } + i++; + } + ca->request_push(); + + } + +} diff --git a/canvas/box_selection.hpp b/canvas/box_selection.hpp new file mode 100644 index 000000000..dcae2046d --- /dev/null +++ b/canvas/box_selection.hpp @@ -0,0 +1,37 @@ +#pragma once +#include "common.hpp" +#include +#include + +namespace horizon { + class BoxSelection { + friend class CanvasGL; + public : + BoxSelection(class CanvasGL *c); + void realize(); + void render(); + void drag_begin(GdkEventButton* button_event); + void drag_end(GdkEventButton* button_event); + void drag_move(GdkEventMotion *motion_event); + + private : + CanvasGL *ca; + + GLuint program; + GLuint vao; + GLuint vbo; + + GLuint screenmat_loc; + GLuint scale_loc; + GLuint offset_loc; + GLuint a_loc; + GLuint b_loc; + + int active; + Coordf sel_a; + Coordf sel_b; + Coordf sel_o; + void update(); + }; + +} diff --git a/canvas/canvas.cpp b/canvas/canvas.cpp new file mode 100644 index 000000000..e7acde1dd --- /dev/null +++ b/canvas/canvas.cpp @@ -0,0 +1,69 @@ +#include "canvas.hpp" +#include +#include + +namespace horizon { + + Canvas::Canvas(): selection_filter(this), selectables(this) {} + + void Canvas::set_layer_display(int index, const LayerDisplay &ld) { + layer_display[index] = ld; + } + + void Canvas::clear() { + selectables.clear(); + triangles.clear(); + targets.clear(); + } + + void Canvas::update(const Symbol &sym) { + clear(); + render(sym); + request_push(); + } + + void Canvas::update(const Sheet &sheet) { + clear(); + render(sheet); + request_push(); + } + + void Canvas::update(const Padstack &padstack) { + clear(); + render(padstack); + request_push(); + } + + void Canvas::update(const Package &pkg) { + clear(); + render(pkg); + request_push(); + } + + void Canvas::update(const Buffer &buf) { + clear(); + render(buf); + request_push(); + } + void Canvas::update(const Board &brd) { + clear(); + render(brd); + request_push(); + } + + void Canvas::set_core(Core *c) { + core = c; + } + + void Canvas::transform_save() { + transforms.push_back(transform); + } + + void Canvas::transform_restore() { + if(transforms.size()) { + transform = transforms.back(); + transforms.pop_back(); + } + } +} + diff --git a/canvas/canvas.hpp b/canvas/canvas.hpp new file mode 100644 index 000000000..88715e039 --- /dev/null +++ b/canvas/canvas.hpp @@ -0,0 +1,210 @@ +#pragma once +#include +#include +#include +#include "common.hpp" +#include "grid.hpp" +#include "box_selection.hpp" +#include "selectables.hpp" +#include "placement.hpp" +#include "target.hpp" +#include "triangle.hpp" +#include "core/core.hpp" +#include "layer_display.hpp" +#include "selection_filter.hpp" +#include "sheet.hpp" + +namespace horizon { + class Canvas: public sigc::trackable { + friend Selectables; + friend class SelectionFilter; + public: + Canvas(); + virtual ~Canvas() {} + void clear(); + void update(const class Symbol &sym); + void update(const class Sheet &sheet); + void update(const class Padstack &padstack); + void update(const class Package &pkg); + void update(const class Buffer &buf); + void update(const class Board &brd); + void set_core(Core *c); + void set_layer_display(int index, const LayerDisplay &ld); + class SelectionFilter selection_filter; + + + protected: + std::vector triangles; + void render(const class Symbol &sym, bool on_sheet = false, bool smashed = false); + void render(const class Junction &junc, bool interactive = true); + void render(const class Line &line, bool interactive = true); + void render(const class SymbolPin &pin, bool interactive = true); + void render(const class Arc &arc, bool interactive = true); + void render(const class Sheet &sheet); + void render(const class SchematicSymbol &sym); + void render(const class LineNet &line); + void render(const class NetLabel &label); + void render(const class BusLabel &label); + void render(const class Warning &warn); + void render(const class PowerSymbol &sym); + void render(const class BusRipper &ripper); + void render(const class Frame &frame); + void render(const class Text &text, bool interactive = true, bool reorient=true); + void render(const class Padstack &padstack, int layer, bool interactive); + void render(const class Padstack &padstack, bool interactive=true); + void render(const class Polygon &polygon, bool interactive=true); + void render(const class Hole &hole, bool interactive=true); + void render(const class Package &package, bool interactive=true); + void render(const class Package &package, int layer, bool interactive=true); + void render(const class Pad &pad, int layer); + void render(const class Buffer &buf); + void render(const class Buffer &buf, int layer); + void render(const class Board &brd); + void render(const class Board &brd, int layer); + void render(const class BoardPackage &pkg); + void render(const class BoardPackage &pkg, int layer); + void render(const class Track &track); + void render(const class Via &via, int layer); + + bool needs_push = true; + virtual void request_push() = 0; + virtual void push() = 0; + + + uint8_t get_triangle_flags_for_line(int layer); + void draw_line(const Coord &a, const Coord &b, const Color &color, bool tr = true, uint64_t width=0, uint8_t flags=0); + void draw_cross(const Coord &o, float size, const Color &color, bool tr = true, uint64_t width=0); + void draw_plus(const Coord &o, float size, const Color &color, bool tr = true, uint64_t width=0); + void draw_box(const Coord &o, float size, const Color &color, bool tr = true, uint64_t width=0); + void draw_arc(const Coord ¢er, float radius, float a0, float a1, const Color &color, bool tr = true, uint64_t width=0); + std::pair draw_text(const Coordf &p, float size, const std::string &text, Orientation orientation, TextPlacement placement, const Color &color, bool tr = true, uint64_t width=0, bool draw=true); + void draw_error(const Coordf ¢er, float scale, const std::string &text, bool tr = true); + std::tuple draw_flag(const Coordf &position, const std::string &txt, int64_t size, Orientation orientation, const Color &c); + + virtual void img_net(const Net *net) {} + virtual void img_polygon(const Polygon &poly) {} + virtual void img_padstack(const Padstack &ps) {} + virtual void img_line(const Coordi &p0, const Coordi &p1, const uint64_t width, int layer=10000); + virtual void img_hole(const Hole &hole) {} + bool img_mode = false; + + + + Placement transform; + void transform_save(); + void transform_restore(); + std::list transforms; + + Selectables selectables; + std::set targets; + Target target_current; + + + Core *core = nullptr; + int work_layer = 0; + std::map layer_display; + + + private: + void img_text_layer(int l); + int img_text_last_layer = 10000; + void img_text_line(const Coordi &p0, const Coordi &p1, const uint64_t width); + }; + + class CanvasGL: public Canvas, public Gtk::GLArea { + friend Grid; + friend BoxSelection; + friend SelectablesRenderer; + friend TriangleRenderer; + public: + CanvasGL(); + + enum class SelectionMode {HOVER, NORMAL, CLARIFY}; + SelectionMode selection_mode = SelectionMode::HOVER; + + std::set get_selection(); + void set_selection(const std::set &sel, bool emit=true); + Coordi get_cursor_pos(); + Coordf get_cursor_pos_win(); + Target get_current_target(); + void set_selection_allowed(bool a); + std::pair get_scale_and_offset(); + void set_scale_and_offset(float sc, Coordf ofs); + + typedef sigc::signal type_signal_selection_changed; + type_signal_selection_changed signal_selection_changed() {return s_signal_selection_changed;} + + typedef sigc::signal type_signal_cursor_moved; + type_signal_cursor_moved signal_cursor_moved() {return s_signal_cursor_moved;} + + void center_and_zoom(const Coordi ¢er); + void zoom_to_bbox(const Coordi &a, const Coordi &b); + + Glib::PropertyProxy property_work_layer() { return p_property_work_layer.get_proxy(); } + Glib::PropertyProxy property_grid_spacing() { return p_property_grid_spacing.get_proxy(); } + Glib::PropertyProxy property_layer_opacity() { return p_property_layer_opacity.get_proxy(); } + + protected: + virtual void push(); + virtual void request_push(); + + private : + static const int MAT3_XX = 0; + static const int MAT3_X0 = 2; + static const int MAT3_YY = 4; + static const int MAT3_Y0 = 5; + + float width, height; + std::array screenmat; + float scale = 1e-5; + Coord offset; + Coord cursor_pos; + Coord cursor_pos_grid; + bool warped = false; + + + Color background_color = Color::new_from_int(0, 24, 64); + Grid grid; + BoxSelection box_selection; + SelectablesRenderer selectables_renderer; + TriangleRenderer triangle_renderer; + + void pan_drag_begin(GdkEventButton* button_event); + void pan_drag_end(GdkEventButton* button_event); + void pan_drag_move(GdkEventMotion *motion_event); + void pan_drag_move(GdkEventScroll *scroll_event); + void pan_zoom(GdkEventScroll *scroll_event, bool to_cursor=true); + void cursor_move(GdkEventMotion *motion_event); + void hover_prelight_update(GdkEventMotion *motion_event); + bool pan_dragging = false; + Coord pan_pointer_pos_orig; + Coord pan_offset_orig; + + Coordf screen2canvas(const Coordf &p) const; + + + bool selection_allowed = true; + Glib::Property p_property_work_layer; + Glib::Property p_property_grid_spacing; + Glib::Property p_property_layer_opacity; + + Gtk::Menu *clarify_menu; + + protected : + virtual void on_size_allocate(Gtk::Allocation &alloc); + virtual void on_realize(); + virtual bool on_render(const Glib::RefPtr &context); + virtual bool on_button_press_event (GdkEventButton* button_event); + virtual bool on_button_release_event (GdkEventButton* button_event); + virtual bool on_motion_notify_event (GdkEventMotion* motion_event); + virtual bool on_scroll_event (GdkEventScroll* scroll_event); + + type_signal_selection_changed s_signal_selection_changed; + type_signal_cursor_moved s_signal_cursor_moved; + + + + }; + + +} diff --git a/canvas/canvas_cairo.cpp b/canvas/canvas_cairo.cpp new file mode 100644 index 000000000..c1482a3ab --- /dev/null +++ b/canvas/canvas_cairo.cpp @@ -0,0 +1,22 @@ +#include "canvas_cairo.hpp" + +namespace horizon { + CanvasCairo::CanvasCairo(Cairo::RefPtr c) : Canvas::Canvas(), cr(c) {} + void CanvasCairo::request_push() { + cr->save(); + cr->scale(2.83465, -2.83465); + cr->translate(0, -209.9); + cr->set_source_rgb(0,0,0); + cr->set_line_width(.05); + cr->set_line_cap(Cairo::LINE_CAP_ROUND); + for(const auto &it: triangles) { + if(isnan(it.y2)) { + cr->set_line_width(std::max(it.x2/1e6, .05)); + cr->move_to(it.x0/1e6,it.y0/1e6); + cr->line_to(it.x1/1e6,it.y1/1e6); + cr->stroke(); + } + } + cr->restore(); + } +} diff --git a/canvas/canvas_cairo.hpp b/canvas/canvas_cairo.hpp new file mode 100644 index 000000000..31f99433f --- /dev/null +++ b/canvas/canvas_cairo.hpp @@ -0,0 +1,18 @@ +#pragma once +#include "canvas.hpp" +#include +#include +#include + + +namespace horizon { + class CanvasCairo: public Canvas { + public : + CanvasCairo(Cairo::RefPtr c); + void push() override {} + void request_push() override; + private : + Cairo::RefPtr cr; + + }; +} diff --git a/canvas/canvas_gl.cpp b/canvas/canvas_gl.cpp new file mode 100644 index 000000000..f3a8a1337 --- /dev/null +++ b/canvas/canvas_gl.cpp @@ -0,0 +1,281 @@ +#include "canvas.hpp" +#include +#include +#include +#include "gl_util.hpp" + +namespace horizon { + std::pair CanvasGL::get_scale_and_offset() { + return std::make_pair(scale, offset); + } + + void CanvasGL::set_scale_and_offset(float sc, Coordf ofs) { + scale = sc; + offset = ofs; + } + + CanvasGL::CanvasGL() : Glib::ObjectBase(typeid(CanvasGL)), Canvas::Canvas(), grid(this), box_selection(this), selectables_renderer(this, &selectables), + triangle_renderer(this, triangles), + p_property_work_layer(*this, "work-layer"), + p_property_grid_spacing(*this, "grid-spacing"), + p_property_layer_opacity(*this, "layer-opacity") + { + add_events( + Gdk::BUTTON_PRESS_MASK| + Gdk::BUTTON_RELEASE_MASK| + Gdk::BUTTON_MOTION_MASK| + Gdk::POINTER_MOTION_MASK| + Gdk::SCROLL_MASK| + Gdk::SMOOTH_SCROLL_MASK| + Gdk::KEY_PRESS_MASK + ); + set_can_focus(true); + property_work_layer().signal_changed().connect([this]{work_layer=property_work_layer();}); + property_grid_spacing().signal_changed().connect([this]{grid.spacing=property_grid_spacing(); queue_draw();}); + property_layer_opacity() = 100; + property_layer_opacity().signal_changed().connect([this]{queue_draw();}); + clarify_menu = Gtk::manage(new Gtk::Menu); + } + + void CanvasGL::on_size_allocate(Gtk::Allocation &alloc) { + width = alloc.get_width(); + height = alloc.get_height(); + + screenmat.fill(0); + screenmat[MAT3_XX] = 2.0/(width*get_scale_factor()); + screenmat[MAT3_X0] = -1; + screenmat[MAT3_YY] = -2.0/(height*get_scale_factor()); + screenmat[MAT3_Y0] = 1; + screenmat[8] = 1; + + + //chain up + Gtk::GLArea::on_size_allocate(alloc); + + } + + void CanvasGL::on_realize() { + Gtk::GLArea::on_realize(); + make_current(); + + grid.realize(); + box_selection.realize(); + selectables_renderer.realize(); + triangle_renderer.realize(); + } + + + bool CanvasGL::on_render(const Glib::RefPtr &context) { + if(needs_push) { + push(); + needs_push = false; + } + glClearColor(background_color.r, background_color.g ,background_color.b, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + grid.render(); + + glEnable (GL_BLEND); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + triangle_renderer.render(); + glDisable(GL_BLEND); + + + selectables_renderer.render(); + box_selection.render(); + + grid.render_cursor(cursor_pos_grid); + glFlush(); + return Gtk::GLArea::on_render(context); + } + + bool CanvasGL::on_button_press_event(GdkEventButton* button_event) { + grab_focus(); + pan_drag_begin(button_event); + box_selection.drag_begin(button_event); + return Gtk::GLArea::on_button_press_event(button_event); + } + + bool CanvasGL::on_motion_notify_event(GdkEventMotion *motion_event) { + grab_focus(); + pan_drag_move(motion_event); + cursor_move(motion_event); + box_selection.drag_move(motion_event); + hover_prelight_update(motion_event); + return Gtk::GLArea::on_motion_notify_event(motion_event); + } + + bool CanvasGL::on_button_release_event(GdkEventButton *button_event) { + pan_drag_end(button_event); + box_selection.drag_end(button_event); + return Gtk::GLArea::on_button_release_event(button_event); + } + + bool CanvasGL::on_scroll_event(GdkEventScroll *scroll_event) { + auto *dev = gdk_event_get_source_device((GdkEvent*)scroll_event); + auto src = gdk_device_get_source(dev); + if(src == GDK_SOURCE_TRACKPOINT) { + if(scroll_event->state & GDK_CONTROL_MASK) { + pan_zoom(scroll_event, false); + } + else { + pan_drag_move(scroll_event); + } + } + else { + pan_zoom(scroll_event); + } + + return Gtk::GLArea::on_scroll_event(scroll_event); + } + + void CanvasGL::cursor_move(GdkEventMotion *motion_event) { + gdouble x,y; + gdk_event_get_coords((GdkEvent*)motion_event, &x, &y); + cursor_pos.x = (x-offset.x)/scale; + cursor_pos.y = (y-offset.y)/-scale; + + + auto sp = grid.spacing; + if(motion_event->state & Gdk::MOD1_MASK) { + sp /= 10; + } + + int64_t xi = round(cursor_pos.x/sp)*sp; + int64_t yi = round(cursor_pos.y/sp)*sp; + Coordi t(xi, yi); + + const auto &f = std::find_if(targets.begin(), targets.end(), [t](const auto &a)->bool{return a.p==t;}); + if(f != targets.end()) { + target_current = *f; + } + else { + target_current = Target(); + } + + const auto sel = get_selection(); + auto target_in_selection = [this, &sel](const Target &ta){ + return std::find_if(sel.begin(), sel.end(), [ta](const auto &a){ + if(ta.type == ObjectType::SYMBOL_PIN && a.type == ObjectType::SCHEMATIC_SYMBOL) { + return ta.path.at(0) == a.uuid; + } + else if(ta.type == ObjectType::PAD && a.type == ObjectType::BOARD_PACKAGE) { + return ta.path.at(0) == a.uuid; + } + else if(ta.type == ObjectType::POLYGON_EDGE && a.type == ObjectType::POLYGON_VERTEX) { + return ta.path.at(0) == a.uuid; + } + return (ta.type == a.type) && (ta.path.at(0) == a.uuid) && (ta.vertex == a.vertex); + }) != sel.end(); + + }; + auto dfn = [this, target_in_selection](const Target &ta) -> float{ + //return inf if target in selection and tool active (selection not allowed) + if(target_in_selection(ta) && !selection_allowed) + return INFINITY; + else + return (cursor_pos-(Coordf)ta.p).mag_sq(); + }; + + auto mi = std::min_element(targets.cbegin(), targets.cend(), [this, dfn](const auto &a, const auto &b){return dfn(a)p; + } + } + + + if(cursor_pos_grid != t) { + s_signal_cursor_moved.emit(t); + } + + + cursor_pos_grid = t; + queue_draw(); + } + + void CanvasGL::request_push() { + needs_push = true; + queue_draw(); + } + + void CanvasGL::center_and_zoom(const Coordi ¢er) { + //we want center to be at width, height/2 + scale = 7.6e-5; + offset.x = -((center.x*scale)-width/2); + offset.y = -((center.y*-scale)-height/2); + queue_draw(); + } + + void CanvasGL::zoom_to_bbox(const Coordi &a, const Coordi &b) { + auto sc_x = width/abs(a.x-b.x); + auto sc_y = height/abs(a.y-b.y); + scale = std::min(sc_x, sc_y); + auto center = (a+b)/2; + offset.x = -((center.x*scale)-width/2); + offset.y = -((center.y*-scale)-height/2); + queue_draw(); + } + + void CanvasGL::push() { + selectables_renderer.push(); + triangle_renderer.push(); + } + + Coordf CanvasGL::screen2canvas(const Coordf &p) const { + Coordf o=p-offset; + o.x /= scale; + o.y /= -scale; + return o; + } + + std::set CanvasGL::get_selection() { + std::set r; + unsigned int i = 0; + for(const auto it: selectables.items) { + if(it.flags&1) { + r.emplace(selectables.items_ref.at(i)); + } + i++; + } + return r; + } + + void CanvasGL::set_selection(const std::set &sel, bool emit) { + for(auto &it:selectables.items) { + it.flags = 0; + } + for(const auto &it:sel) { + const auto &f = std::find_if(selectables.items_ref.begin(), selectables.items_ref.end(), [it](const auto &a)->bool{return (a.uuid==it.uuid) && (a.vertex == it.vertex) && (a.type == it.type);}); + if(f != selectables.items_ref.end()) { + const auto n = f-selectables.items_ref.begin(); + selectables.items[n].flags = 1; + } + } + if(emit) + s_signal_selection_changed.emit(); + request_push(); + } + + void CanvasGL::set_selection_allowed(bool a) { + selection_allowed = a; + } + + Coordi CanvasGL::get_cursor_pos() { + return cursor_pos_grid; + } + + Coordf CanvasGL::get_cursor_pos_win() { + Coordf r; + r.x = cursor_pos.x*scale+offset.x; + r.y = cursor_pos.y*-scale+offset.y; + return r; + } + + Target CanvasGL::get_current_target() { + return target_current; + } + +} diff --git a/canvas/draw.cpp b/canvas/draw.cpp new file mode 100644 index 000000000..9ba952541 --- /dev/null +++ b/canvas/draw.cpp @@ -0,0 +1,123 @@ +#include "canvas.hpp" +#include + +namespace horizon { + void Canvas::draw_line(const Coordf &a, const Coordf &b, const Color &color, bool tr, uint64_t width, uint8_t flags) { + auto pa = a; + auto pb = b; + if(tr) { + pa = transform.transform(a); + pb = transform.transform(b); + } + triangles.emplace_back(pa, pb, Coordf(width, NAN), color, flags); + } + + void Canvas::draw_cross(const Coordf &p, float size, const Color &color, bool tr, uint64_t width) { + draw_line(p+Coordf(-size, size), p+Coordf(size, -size), color, tr, width); + draw_line(p+Coordf(-size, -size), p+Coordf(size, size), color, tr, width); + } + void Canvas::draw_plus(const Coordf &p, float size, const Color &color, bool tr, uint64_t width) { + draw_line(p+Coordf(0, size), p+Coordf(0, -size), color, tr, width); + draw_line(p+Coordf(-size, 0), p+Coordf(size, 0), color, tr, width); + } + void Canvas::draw_box(const Coordf &p, float size, const Color &color, bool tr, uint64_t width) { + draw_line(p+Coordf(-size, size), p+Coordf(size, size), color, tr, width); + draw_line(p+Coordf(size, size), p+Coordf(size, -size), color, tr, width); + draw_line(p+Coordf(size, -size), p+Coordf(-size, -size), color, tr, width); + draw_line(p+Coordf(-size, -size), p+Coordf(-size, size), color, tr, width); + } + + void Canvas::draw_arc(const Coordf ¢er, float radius, float a0, float a1, const Color &color, bool tr, uint64_t width) { + unsigned int segments = 64; + if(a0 < 0) { + a0 += 2*M_PI; + } + if(a1 < 0) { + a1 += 2*M_PI; + } + float dphi = a1-a0; + if(dphi < 0) { + dphi += 2*M_PI; + } + dphi /= segments; + while(segments--) { + draw_line(center+Coordf::euler(radius, a0), center+Coordf::euler(radius, a0+dphi), color, tr, width); + a0 += dphi; + } + } + + void Canvas::draw_error(const Coordf ¢er, float sc, const std::string &text, bool tr) { + float x = center.x; + float y = center.y; + y -= 3*sc; + Color c(1,0,0); + draw_line({x-5*sc, y}, {x+5*sc, y}, c, tr); + draw_line({x-5*sc, y}, {x, y+0.8660f*10*sc}, c, tr); + draw_line({x+5*sc, y}, {x, y+0.8660f*10*sc}, c, tr); + draw_line({x, y+0.5f*sc}, {x+1*sc, y+1.5f*sc}, c, tr); + draw_line({x, y+0.5f*sc}, {x-1*sc, y+1.5f*sc}, c, tr); + draw_line({x, y+2.5f*sc}, {x+1*sc, y+1.5f*sc}, c, tr); + draw_line({x, y+2.5f*sc}, {x-1*sc, y+1.5f*sc}, c, tr); + draw_line({x, y+3*sc}, {x+1*sc, y+6*sc}, c, tr); + draw_line({x, y+3*sc}, {x-1*sc, y+6*sc}, c, tr); + draw_line({x-1*sc, y+6*sc}, {x+1*sc, y+6*sc}, c, tr); + draw_text({x-5*sc, y-1.5f*sc}, 0.25_mm, text, Orientation::RIGHT, TextPlacement::BASELINE, c, tr); + } + + std::tuple Canvas::draw_flag(const Coordf &position, const std::string &txt, int64_t size, Orientation orientation, const Color &c) { + Coordi shift; + int64_t distance = size/1; + switch(orientation) { + case Orientation::LEFT : + shift.x = -distance; + break; + case Orientation::RIGHT : + shift.x = distance; + break; + case Orientation::UP : + shift.y = distance; + break; + case Orientation::DOWN: + shift.y = -distance; + break; + } + + double enlarge = size/4; + auto extents = draw_text(position+shift, size, txt, orientation, TextPlacement::CENTER, c, false); + extents.first -= Coordf(enlarge, enlarge); + extents.second += Coordf(enlarge, enlarge); + switch(orientation) { + case Orientation::LEFT : + draw_line(extents.first, {extents.first.x, extents.second.y}, c); + draw_line({extents.first.x, extents.second.y}, extents.second, c); + draw_line(extents.second, position, c); + draw_line(position, {extents.second.x, extents.first.y}, c); + draw_line({extents.second.x, extents.first.y}, extents.first, c); + break; + + case Orientation::RIGHT : + draw_line(extents.second, {extents.second.x, extents.first.y}, c); + draw_line({extents.first.x, extents.second.y}, extents.second, c); + draw_line(extents.first, position, c); + draw_line(position, {extents.first.x, extents.second.y}, c); + draw_line({extents.second.x, extents.first.y}, extents.first, c); + break; + case Orientation::UP : + draw_line(position, extents.first, c); + draw_line(position, {extents.second.x, extents.first.y}, c); + draw_line(extents.first, {extents.first.x, extents.second.y}, c); + draw_line({extents.second.x, extents.first.y}, extents.second, c); + draw_line(extents.second, {extents.first.x, extents.second.y}, c); + break; + case Orientation::DOWN : + draw_line(position, extents.second, c); + draw_line(position, {extents.first.x, extents.second.y}, c); + draw_line(extents.first, {extents.first.x, extents.second.y}, c); + draw_line({extents.second.x, extents.first.y}, extents.second, c); + draw_line(extents.first, {extents.second.x, extents.first.y}, c); + break; + } + return std::make_tuple(extents.first, extents.second, shift); + } + +} diff --git a/canvas/gl_util.cpp b/canvas/gl_util.cpp new file mode 100644 index 000000000..8249e7763 --- /dev/null +++ b/canvas/gl_util.cpp @@ -0,0 +1,106 @@ +#include "gl_util.hpp" +#include +#include +#include + +namespace horizon { + /* Create and compile a shader */ + static GLuint create_shader(int type, const char *src) + { + auto shader = glCreateShader(type); + glShaderSource(shader, 1, &src, nullptr); + glCompileShader(shader); + + int status; + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if(status == GL_FALSE) + { + int log_len; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len); + + std::string log_space(log_len+1, ' '); + glGetShaderInfoLog(shader, log_len, nullptr, (GLchar*)log_space.c_str()); + + std::cerr << "Compile failure in " << + (type == GL_VERTEX_SHADER ? "vertex" : "fragment") << + " shader: " << log_space << std::endl; + + glDeleteShader(shader); + + return 0; + } + + return shader; + } + + static GLuint create_shader_from_resource(int type, const char *resource) { + auto shader_bytes = Gio::Resource::lookup_data_global(resource); + gsize shader_size {shader_bytes->get_size()}; + return create_shader(type, (const char*)shader_bytes->get_data(shader_size)); + } + + + GLuint gl_create_program_from_resource(const char *vertex_resource, const char *fragment_resource, const char *geometry_resource) { + GLuint vertex, fragment, geometry=0; + GLuint program = 0; + int status; + vertex = create_shader_from_resource (GL_VERTEX_SHADER, vertex_resource); + + if (vertex == 0) { + return 0; + } + + fragment = create_shader_from_resource (GL_FRAGMENT_SHADER, fragment_resource); + + if (fragment == 0) { + glDeleteShader (vertex); + return 0; + } + + if(geometry_resource) { + geometry = create_shader_from_resource (GL_GEOMETRY_SHADER, geometry_resource); + if(geometry == 0) { + glDeleteShader(vertex); + glDeleteShader(fragment); + } + } + + program = glCreateProgram(); + glAttachShader(program, vertex); + glAttachShader(program, fragment); + if(geometry) { + glAttachShader(program, geometry); + } + + glLinkProgram(program); + + glGetProgramiv (program, GL_LINK_STATUS, &status); + if (status == GL_FALSE) { + int log_len; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_len); + + std::string log_space(log_len+1, ' '); + glGetProgramInfoLog(program, log_len, nullptr, (GLchar*)log_space.c_str()); + + std::cerr << "Linking failure: " << log_space << std::endl; + + glDeleteProgram(program); + program = 0; + + goto out; + } + + glDetachShader (program, vertex); + glDetachShader (program, fragment); + if(geometry) + glDetachShader (program, geometry); + + out: + glDeleteShader (vertex); + glDeleteShader (fragment); + if(geometry) + glDeleteShader (geometry); + + return program; + } +} diff --git a/canvas/gl_util.hpp b/canvas/gl_util.hpp new file mode 100644 index 000000000..23126f55a --- /dev/null +++ b/canvas/gl_util.hpp @@ -0,0 +1,13 @@ +#include + +namespace horizon { + //GLuint gl_create_shader (int type, char *src); + GLuint gl_create_program_from_resource(const char *vertex_resource, const char *fragment_resource, const char *geometry_resource); + #define GET_LOC(d, loc) do { \ + d->loc ## _loc = glGetUniformLocation(d->program, #loc); \ + } while(0) ; + + #define GET_LOC2(d, loc) do { \ + (d).loc ## _loc = glGetUniformLocation((d).program, #loc); \ + } while(0) ; +} diff --git a/canvas/grid.cpp b/canvas/grid.cpp new file mode 100644 index 000000000..ca38bb491 --- /dev/null +++ b/canvas/grid.cpp @@ -0,0 +1,152 @@ +#include "grid.hpp" +#include "gl_util.hpp" +#include "canvas.hpp" + +namespace horizon { + Grid::Grid(class CanvasGL *c): ca(c), spacing(1.25_mm), mark_size(5), color(Color::new_from_int(0, 51, 136)) { + + } + + + static GLuint create_vao (GLuint program) { + GLuint position_index = glGetAttribLocation (program, "position"); + GLuint vao, buffer; + + /* we need to create a VAO to store the other buffers */ + glGenVertexArrays (1, &vao); + glBindVertexArray (vao); + + /* this is the VBO that holds the vertex data */ + glGenBuffers (1, &buffer); + glBindBuffer (GL_ARRAY_BUFFER, buffer); + //data is buffered lateron + + + static const GLfloat vertices[] = { + 0, -1, + 0, 1, + -1, 0, + 1, 0, + .2, -.2, + -.2, -.2, + -.2, -.2, + -.2, .2, + -.2, .2, + .2, .2, + .2, .2, + .2, -.2, + }; + glBufferData (GL_ARRAY_BUFFER, sizeof (vertices), vertices, GL_STATIC_DRAW); + + /* enable and set the position attribute */ + glEnableVertexAttribArray (position_index); + glVertexAttribPointer (position_index, 2, GL_FLOAT, GL_FALSE, + 2*sizeof (GLfloat), + 0); + + /* enable and set the color attribute */ + /* reset the state; we will re-enable the VAO when needed */ + glBindBuffer (GL_ARRAY_BUFFER, 0); + glBindVertexArray (0); + + glDeleteBuffers (1, &buffer); + return vao; + } + + void Grid::realize() { + program = gl_create_program_from_resource("/net/carrotIndustries/horizon/canvas/shaders/grid-vertex.glsl", "/net/carrotIndustries/horizon/canvas/shaders/grid-fragment.glsl", nullptr); + vao = create_vao(program); + + GET_LOC(this, screenmat); + GET_LOC(this, scale); + GET_LOC(this, offset); + GET_LOC(this, grid_size); + GET_LOC(this, grid_0); + GET_LOC(this, grid_mod); + GET_LOC(this, mark_size); + GET_LOC(this, color); + } + + void Grid::render() { + glUseProgram(program); + glBindVertexArray (vao); + glUniformMatrix3fv(screenmat_loc, 1, GL_TRUE, ca->screenmat.data()); + glUniform1f(scale_loc, ca->scale); + glUniform2f(offset_loc, ca->offset.x, ca->offset.y); + glUniform1f(mark_size_loc, mark_size); + glUniform3f(color_loc, color.r, color.g, color.b); + + Coord grid_0; + grid_0.x = (round((-ca->offset.x/ca->scale)/spacing)-1)*spacing; + grid_0.y = (round((-(ca->height-ca->offset.y)/ca->scale)/spacing)-1)*spacing; + + + float sp = spacing; + + int n; + do { + glUniform1f(grid_size_loc, sp); + glUniform2f(grid_0_loc, grid_0.x, grid_0.y); + int mod = (ca->width/ca->scale)/sp+4; + glUniform1i(grid_mod_loc, mod); + n = mod * ((ca->height/ca->scale)/sp+4); + sp*=2; + } while(n>.5e6); + + glLineWidth(1); + glDrawArraysInstanced (GL_LINES, 0, 4, n); + + + //draw origin + grid_0.x = 0; + grid_0.y = 0; + + glUniform1f(grid_size_loc, 0); + glUniform2f(grid_0_loc, grid_0.x, grid_0.y); + glUniform1i(grid_mod_loc, 1); + glUniform1f(mark_size_loc, 15); + glUniform3f(color_loc, 0, 1, 0); + + glLineWidth(1); + glDrawArraysInstanced (GL_LINES, 0, 4, 1); + + glBindVertexArray (0); + glUseProgram (0); + } + + void Grid::render_cursor(Coord &coord) { + glUseProgram(program); + glBindVertexArray (vao); + glUniformMatrix3fv(screenmat_loc, 1, GL_TRUE, ca->screenmat.data()); + glUniform1f(scale_loc, ca->scale); + glUniform2f(offset_loc, ca->offset.x, ca->offset.y); + glUniform1f(mark_size_loc, 20); + + + + + + glUniform1f(grid_size_loc, 0); + glUniform2f(grid_0_loc, coord.x, coord.y); + glUniform1i(grid_mod_loc, 1); + + + + glUniform3f(color_loc, ca->background_color.r, ca->background_color.g, ca->background_color.b); + glLineWidth(4); + glDrawArraysInstanced (GL_LINES, 0, 12, 1); + + if(ca->target_current.is_valid()) { + glUniform3f(color_loc, 1, 0, 0); + } + else { + glUniform3f(color_loc, 0, 1, 0); + } + glLineWidth(1); + glDrawArraysInstanced (GL_LINES, 0, 12, 1); + + glBindVertexArray (0); + glUseProgram (0); + } + +} diff --git a/canvas/grid.hpp b/canvas/grid.hpp new file mode 100644 index 000000000..e1a09405f --- /dev/null +++ b/canvas/grid.hpp @@ -0,0 +1,34 @@ +#pragma once +#include "common.hpp" +#include + +namespace horizon { + class Grid { + friend class CanvasGL; + public : + Grid(class CanvasGL *c); + void realize(); + void render(); + void render_cursor(Coord &coord); + + private : + CanvasGL *ca; + int64_t spacing; + float mark_size; + Color color; + + GLuint program; + GLuint vao; + GLuint vbo; + + GLuint screenmat_loc; + GLuint scale_loc; + GLuint offset_loc; + GLuint grid_size_loc; + GLuint grid_0_loc; + GLuint grid_mod_loc; + GLuint mark_size_loc; + GLuint color_loc; + }; + +} diff --git a/canvas/hershey_fonts.cpp b/canvas/hershey_fonts.cpp new file mode 100644 index 000000000..0af1574ae --- /dev/null +++ b/canvas/hershey_fonts.cpp @@ -0,0 +1,3352 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2000-2008, Intel Corporation, all rights reserved. +// Copyright (C) 2009, Willow Garage Inc., all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + +/* //////////////////////////////////////////////////////////////////// +// +// CvMat helper tables +// +// */ +namespace horizon { +const char* hershey_glyphs[] = { + "", + "MWRMNV RMVV PSTS", + "MWOMOV OMSMUNUPSQ OQSQURUUSVOV", + "MXVNTMRMPNOPOSPURVTVVU", + "MWOMOV OMRMTNUPUSTURVOV", + "MWOMOV OMUM OQSQ OVUV", + "MVOMOV OMUM OQSQ", + "MXVNTMRMPNOPOSPURVTVVUVR SRVR", + "MWOMOV UMUV OQUQ", + "PTRMRV", + "NUSMSTRVPVOTOS", + "MWOMOV UMOS QQUV", + "MVOMOV OVUV", + "LXNMNV NMRV VMRV VMVV", + "MWOMOV OMUV UMUV", + "MXRMPNOPOSPURVSVUUVSVPUNSMRM", + "MWOMOV OMSMUNUQSROR", + "MXRMPNOPOSPURVSVUUVSVPUNSMRM STVW", + "MWOMOV OMSMUNUQSROR RRUV", + "MWUNSMQMONOOPPTRUSUUSVQVOU", + "MWRMRV NMVM", + "MXOMOSPURVSVUUVSVM", + "MWNMRV VMRV", + "LXNMPV RMPV RMTV VMTV", + "MWOMUV UMOV", + "MWNMRQRV VMRQ", + "MWUMOV OMUM OVUV", + "MWRMNV RMVV PSTS", + "MWOMOV OMSMUNUPSQ OQSQURUUSVOV", + "MVOMOV OMUM", + "MWRMNV RMVV NVVV", + "MWOMOV OMUM OQSQ OVUV", + "MWUMOV OMUM OVUV", + "MWOMOV UMUV OQUQ", + "MXRMPNOPOSPURVSVUUVSVPUNSMRM QQTR TQQR", + "PTRMRV", + "MWOMOV UMOS QQUV", + "MWRMNV RMVV", + "LXNMNV NMRV VMRV VMVV", + "MWOMOV OMUV UMUV", + "MWOMUM PQTR TQPR OVUV", + "MXRMPNOPOSPURVSVUUVSVPUNSMRM", + "MWOMOV UMUV OMUM", + "MWOMOV OMSMUNUQSROR", + "MWOMRQOV OMUM OVUV", + "MWRMRV NMVM", + "MWNONNOMPMQNRPRV VOVNUMTMSNRP", + "LXRMRV PONPNSPTTTVSVPTOPO", + "MWOMUV UMOV", + "LXRMRV NOOPOSQTSTUSUPVO", + "MXOVQVOROPPNRMSMUNVPVRTVVV", + "MWSMMV SMUV OSTS", + "MWQMNV QMTMVNVPSQPQ SQURUTTURVNV", + "LXVPUNTMRMPNOONQNSOUPVRVTUUT", + "MXQMNV QMUMVOVQUTTURVNV", + "MVQMNV QMVM PQSQ NVSV", + "MVQMNV QMVM PQSQ", + "LXVPUNTMRMPNOONQNSOUPVRVTUUSRS", + "MXQMNV WMTV PQUQ", + "PUTMQV", + "OVUMSSRUQVPVOUOT", + "MVQMNV VMOS RQTV", + "NVRMOV OVTV", + "LYPMMV PMQV XMQV XMUV", + "MXQMNV QMTV WMTV", + "LXRMPNOONQNSOUPVRVTUUTVRVPUNTMRM", + "MWQMNV QMUMVNVPUQSRPR", + "LXRMPNOONQNSOUPVRVTUUTVRVPUNTMRM QVPUPTQSRSSTTVUWVW", + "MWQMNV QMUMVNVPUQSRPR QRRUSVTVUU", + "MWVNTMRMPNPPQQTRUSUUSVPVNU", + "MVSMPV PMVM", + "LXPMNSNUOVRVTUUSWM", + "MWOMQV WMQV", + "KXNMNV SMNV SMSV XMSV", + "NWQMTV WMNV", + "NWQMSQQV WMSQ", + "MWQMWMNVTV", + "", + "", + "", + "", + "", + "", + "LXNMRV VMRV NMVM", + "MWNLVX", + "LXRONU ROVU", + "MWNVVV", + "PVRMUQ", + "MWMMOKQKTMVMWK", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "NWQPTPUQUV URQSPTPUQVSVUU", + "MWOMOV OSPURVTUUSTQRPPQOS", + "MWUQSPRPPQOSPURVSVUU", + "MWUMUV USTQRPPQOSPURVTUUS", + "MWOSUSTQRPPQOSPURVTV", + "NVUNTMSMRNRV PPTP", + "MWUPUVTXRYPY USTQRPPQOSPURVTUUS", + "MWOMOV OSPQRPTQUSUV", + "PTRLQMRNSMRL RPRV", + "PUSLRMSNTMSL SPSXRYQYPX", + "NWPMPV UPPT RSUV", + "PTRMRV", + "KYMPMV MSNQOPPPQQRSRV RSSQTPUPVQWSWV", + "MWOPOV OSPQRPTQUSUV", + "MWRPPQOSPURVTUUSTQRP", + "MWOPOY OSPURVTUUSTQRPPQOS", + "MWUPUY USTQRPPQOSPURVTUUS", + "NVPPPV PSQQSPTP", + "NWUQTPQPPQPRQSTSUTUUTVQVPU", + "NVRMRUSVTVUU PPTP", + "MWUPUV OPOSPURVTUUS", + "NVOPRV UPRV", + "LXNPPV RPPV RPTV VPTV", + "MWOPUV UPOV", + "MWOPRV UPRVQXPYOY", + "MWOPUPOVUV", + "MXVPUSTURVPUOSPQRPTQUUVV", + "MWOTQVSVTUTSSRPQRQTPUOUNTMRMQNPPOTNY", + "MXNQOPQPRQRSQW VPURSTQWPY", + "MWTNSMRMQNQORPTQUSTURVPUOSPQRP", + "NWUQSPQPPQPRQS SSQSPTPUQVSVUU", + "NWTMSNSOTP UPSPQQPSPUQVSWSXRYQY", + "LXNQOPPPQQQSPV QSRQTPUPVQVSUVTY", + "LXNQOPPPQQQURVSVTUUSVPVNUMTMSNSPTRUSWT", + "OVRPQSQURVSVTU", + "MWQPOV UPTPRQPS PSQUSVTV", + "MWOMPMQNRPUV RPOV", + "LYPPMY UPTSSUQVPVOUOS TSTUUVVVWU", + "MWNPOPOV UPTSRUOV", + "NWTMSNSOTP UPSPQQQRRSTS SSQTPUPVQWSXSYRZQZ", + "MWRPPQOSPURVTUUSTQRP", + "MXOQQPVP QPQRPV TPTRUV", + "MWOSPURVTUUSTQRPPQOSNY", + "MXVPRPPQOSPURVTUUSTQRP", + "MXOQQPVP SPRV", + "KXMQNPOPPQPUQVSVTUUSVP", + "MXPPOQOSPURVSVUUVSVQUPTPSQRSQY", + "MWOPPPQQSXTYUY UPTRPWOY", + "KYTMRY MQNPOPPQPUQVTVUUVSWP", + "LXOPNRNTOVQVRTRR UPVRVTUVSVRT", + "LWTSSQQPOQNSOUQVSUTS UPTSTUUVVV", + "MWQMOSPURVTUUSTQRPPQOS", + "MWUQSPRPPQOSPURVTV", + "LWTSSQQPOQNSOUQVSUTS VMTSTUUVVV", + "MWOSTSURUQSPRPPQOSPURVTV", + "OVVMUMTNSPQVPXOYNY QPUP", + "MXUSTQRPPQOSPURVTUUS VPTVSXRYPYOX", + "MVQMNV OSPQQPSPTQTRSTSUTVUV", + "PUSMSNTNTMSM QPRPSQSRRTRUSVTV", + "OUSMSNTNTMSM QPRPSQSRRVQXPYOYNX", + "NVRMOV UPTPRQPS PSQUSVTV", + "OTSMQSQURVSV", + "JYKPLPMQMSLV MSNQOPQPRQRSQV RSSQTPVPWQWRVTVUWVXV", + "MWNPOPPQPSOV PSQQRPTPUQURTTTUUVVV", + "MWRPPQOSPURVTUUSTQRP", + "MXNPOPPQPSNY PSQUSVUUVSUQSPQQPS", + "MXUSTQRPPQOSPURVTUUS VPSY", + "MVOPPPQQQSPV UQTPSPRQQS", + "NVTQSPQPPQPRQSRSSTSURVPVOU", + "NUSMQSQURVSV PPTP", + "MWNPOPPQPROTOUPVRVSUTS UPTSTUUVVV", + "MWNPOPPQPROTOUPVRVTUURUP", + "KYLPMPNQNRMTMUNVPVQURSSP RSRUSVUVVUWRWP", + "MWOQPPQPRQRUSVTVUU VQUPTPSQQUPVOVNU", + "MWNPOPPQPROTOUPVRVSUTS UPSVRXQYOYNX", + "NVUPOV PQQPSPTQ PUQVSVTU", + "", + "", + "", + "", + "", + "", + "MWUSTQRPPQOSPURVTUUSUPTNRMQM", + "MWUQSPRPPQOSPURVSVUU OSSS", + "MWRMQNPPOSOVPWRWSVTTUQUNTMRM PRTR", + "MWTMQY RPPQOSPURVSVUUVSUQSPRP", + "MWUQSPQPOQOSPTRUSVSWRXQX", + "", + "", + "KYTPTSUTVTWSWQVOUNSMQMONNOMQMSNUOVQWSWUV TQSPQPPQPSQTSTTS", + "MWUNORUV", + "MWONUROV", + "OUTKQKQYTY", + "OUPKSKSYPY", + "OUTKSLRNROSQQRSSRURVSXTY", + "OUPKQLRNROQQSRQSRURVQXPY", + "LYPMQNQOPPOPNONNOMPMSNUNWMNV USTTTUUVVVWUWTVSUS", + "PT", + "NV", + "MWRMPNOPOSPURVTUUSUPTNRM", + "MWPORMRV", + "MWONQMSMUNUPTROVUV", + "MWONQMSMUNUPSQ RQSQURUUSVQVOU", + "MWSMSV SMNSVS", + "MWPMOQQPRPTQUSTURVQVOU PMTM", + "MWTMRMPNOPOSPURVTUUSTQRPPQOS", + "MWUMQV OMUM", + "MWQMONOPQQSQUPUNSMQM QQOROUQVSVUUURSQ", + "MWUPTRRSPROPPNRMTNUPUSTURVPV", + "PURURVSVSURU", + "PUSVRVRUSUSWRY", + "PURPRQSQSPRP RURVSVSURU", + "PURPRQSQSPRP SVRVRUSUSWRY", + "PURMRR SMSR RURVSVSURU", + "NWPNRMSMUNUPRQRRSRSQUP RURVSVSURU", + "PTRMRQ", + "NVPMPQ TMTQ", + "NVQMPNPPQQSQTPTNSMQM", + "MWRKRX UNSMQMONOPQQTRUSUUSVQVOU", + "MWVLNX", + "OUTKRNQQQSRVTY", + "OUPKRNSQSSRVPY", + "PTRKRY", + "LXNRVR", + "LXRNRV NRVR", + "LXNPVP NTVT", + "MWOOUU UOOU", + "MWRORU OPUT UPOT", + "PURQRRSRSQRQ", + "PUSMRORQSQSPRP", + "PUSNRNRMSMSORQ", + "LXSOVRSU NRVR", + "MXQLQY TLTY OQVQ OTVT", + "LXVRURTSSURVOVNUNSORRQSPSNRMPMONOPQSSUUVVV", + "LXNNOQOSNV VNUQUSVV NNQOSOVN NVQUSUVV", + "LYRQQPOPNQNSOTQTRSSQTPVPWQWSVTTTSSRQ", + "", + "H\\NRMQLRMSNR VRWQXRWSVR", + "H\\MPLQLRMSNSOROQNPMP MQMRNRNQMQ WPVQVRWSXSYRYQXPWP WQWRXRXQWQ", + "I[KRYR", + "", + "H\\RUJPRTZPRU", + "", + "", + "", + "", + "", + "F^ISJQLPNPPQTTVUXUZT[Q ISJPLONOPPTSVTXTZS[Q IYJWLVNVPWTZV[X[ZZ[W IYJVLUNUPVTYVZXZZY[W", + "", + "F^ISJQLPNPPQTTVUXUZT[Q ISJPLONOPPTSVTXTZS[Q IW[W I[[[", + "", + "CaGO]OXI L[GU]U", + "", + "D`F^^^^FFFF^", + "", + "KYQVOUNSNQOOQNSNUOVQVSUUSVQV SVVS QVVQ OUUO NSSN NQQN", + "", + "H\\IR[R", + "H\\IR[R IQ[Q", + "", + "LYPFSCSP RDRP OPVP MRXR OVOWNWNVOUQTTTVUWWVYTZQ[O\\N^Na TTUUVWUYTZ N`O_P_S`V`W_ P_SaVaW_W^", + "LYPFSCSP RDRP OPVP MRXR OVOWNWNVOUQTTTVUWWVYTZ TTUUVWUYTZ RZTZV[W]W^V`TaQaO`N_N^O^O_ TZU[V]V^U`Ta", + "LYPFSCSP RDRP OPVP MRXR VVVWWWWVVUTTRTPUOVNYN^O`QaTaV`W^W\\VZTYQYN[ RTPVOYO^P`Qa TaU`V^V\\UZTY", + "LYPFSCSP RDRP OPVP MRXR QTOUNWOYQZTZVYWWVUTTQT QTPUOWPYQZ TZUYVWUUTT QZO[N]N^O`QaTaV`W^W]V[TZ QZP[O]O^P`Qa TaU`V^V]U[TZ", + "LYOEOFNFNEODQCTCVDWFVHTIQJOKNMNP TCUDVFUHTI NOONPNSOVOWN PNSPVPWNWM MRXR OVOWNWNVOUQTTTVUWWVYTZ TTUUVWUYTZ RZTZV[W]W^V`TaQaO`N_N^O^O_ TZU[V]V^U`Ta", + "LYOEOFNFNEODQCTCVDWFVHTI TCUDVFUHTI RITIVJWLWMVOTPQPOONNNMOMON TIUJVLVMUOTP MRXR QTOUNWOYQZTZVYWWVUTTQT QTPUOWPYQZ TZUYVWUUTT QZO[N]N^O`QaTaV`W^W]V[TZ QZP[O]O^P`Qa TaU`V^V]U[TZ", + "LYOCNI OCVC ODSDVC NIOHQGTGVHWJWMVOTPQPOONNNMOMON TGUHVJVMUOTP MRXR QTOUNWOYQZTZVYWWVUTTQT QTPUOWPYQZ TZUYVWUUTT QZO[N]N^O`QaTaV`W^W]V[TZ QZP[O]O^P`Qa TaU`V^V]U[TZ", + "LYNCNG VERLPP WCTIQP NEPCRCUE NEPDRDUEVE MRXR QTOUNWOYQZTZVYWWVUTTQT QTPUOWPYQZ TZUYVWUUTT QZO[N]N^O`QaTaV`W^W]V[TZ QZP[O]O^P`Qa TaU`V^V]U[TZ", + "LYOCNI OCVC ODSDVC NIOHQGTGVHWJWMVOTPQPOONNNMOMON TGUHVJVMUOTP MRXR VVVWWWWVVUTTRTPUOVNYN^O`QaTaV`W^W\\VZTYQYN[ RTPVOYO^P`Qa TaU`V^V\\UZTY", + "LYPFSCSP RDRP OPVP MRXR SVSa TTTa TTM]X] QaVa", + "LYOEOFNFNEODQCTCVDWFVHTI TCUDVFUHTI RITIVJWLWMVOTPQPOONNNMOMON TIUJVLVMUOTP MRXR SVSa TTTa TTM]X] QaVa", + "F^YXWZU[R[PZMXKWIWHXHZI[K[MZOWPURQTKWGYFZF[G\\H[IZH[G[FZFYFWGVHTLRPPVNZMZ OPUP", + "E^P[MZJXHUGRGOHLJIMGPFTFWGYI[L\\O\\R[UYXVZS[P[ NJNW OJOW LJSJVKWMWNVPSQOQ SJUKVMVNUPSQ LWQW SQTRUVVWWWXV SQURVVWW", + "E^P[MZJXHUGRGOHLJIMGPFTFWGYI[L\\O\\R[UYXVZS[P[ UKVJVNUKSJPJNKMLLOLRMUNVPWSWUVVT PJNLMOMRNUPW", + "E_IM[M IR[R IW[W K[YI", + "CaHQGRHSIRHQ RQQRRSSRRQ \\Q[R\\S]R\\Q", + "", + "E_NWLTIRLPNM LPJRLT JRZR VWXT[RXPVM XPZRXT", + "JZWNTLRIPLMN PLRJTL RJRZ WVTXR[PXMV PXRZTX", + "F^ZJSJOKMLKNJQJSKVMXOYSZZZ SFS^", + "F^JJQJUKWLYNZQZSYVWXUYQZJZ QFQ^", + "F^JJQJUKWLYNZQZSYVWXUYQZJZ ORZR", + "", + "H\\LBL[ RBR[ XBX[", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "I[RFJ[ RFZ[ MTWT", + "G\\KFK[ KFTFWGXHYJYLXNWOTP KPTPWQXRYTYWXYWZT[K[", + "H]ZKYIWGUFQFOGMILKKNKSLVMXOZQ[U[WZYXZV", + "G\\KFK[ KFRFUGWIXKYNYSXVWXUZR[K[", + "H[LFL[ LFYF LPTP L[Y[", + "HZLFL[ LFYF LPTP", + "H]ZKYIWGUFQFOGMILKKNKSLVMXOZQ[U[WZYXZVZS USZS", + "G]KFK[ YFY[ KPYP", + "NVRFR[", + "JZVFVVUYTZR[P[NZMYLVLT", + "G\\KFK[ YFKT POY[", + "HYLFL[ L[X[", + "F^JFJ[ JFR[ ZFR[ ZFZ[", + "G]KFK[ KFY[ YFY[", + "G]PFNGLIKKJNJSKVLXNZP[T[VZXXYVZSZNYKXIVGTFPF", + "G\\KFK[ KFTFWGXHYJYMXOWPTQKQ", + "G]PFNGLIKKJNJSKVLXNZP[T[VZXXYVZSZNYKXIVGTFPF SWY]", + "G\\KFK[ KFTFWGXHYJYLXNWOTPKP RPY[", + "H\\YIWGTFPFMGKIKKLMMNOOUQWRXSYUYXWZT[P[MZKX", + "JZRFR[ KFYF", + "G]KFKULXNZQ[S[VZXXYUYF", + "I[JFR[ ZFR[", + "F^HFM[ RFM[ RFW[ \\FW[", + "H\\KFY[ YFK[", + "I[JFRPR[ ZFRP", + "H\\YFK[ KFYF K[Y[", + "I[RFJ[ RFZ[ MTWT", + "G\\KFK[ KFTFWGXHYJYLXNWOTP KPTPWQXRYTYWXYWZT[K[", + "HYLFL[ LFXF", + "I[RFJ[ RFZ[ J[Z[", + "H[LFL[ LFYF LPTP L[Y[", + "H\\YFK[ KFYF K[Y[", + "G]KFK[ YFY[ KPYP", + "G]PFNGLIKKJNJSKVLXNZP[T[VZXXYVZSZNYKXIVGTFPF OPUP", + "NVRFR[", + "G\\KFK[ YFKT POY[", + "I[RFJ[ RFZ[", + "F^JFJ[ JFR[ ZFR[ ZFZ[", + "G]KFK[ KFY[ YFY[", + "I[KFYF OPUP K[Y[", + "G]PFNGLIKKJNJSKVLXNZP[T[VZXXYVZSZNYKXIVGTFPF", + "G]KFK[ YFY[ KFYF", + "G\\KFK[ KFTFWGXHYJYMXOWPTQKQ", + "I[KFRPK[ KFYF K[Y[", + "JZRFR[ KFYF", + "I[KKKILGMFOFPGQIRMR[ YKYIXGWFUFTGSIRM", + "H\\RFR[ PKMLLMKOKRLTMUPVTVWUXTYRYOXMWLTKPK", + "H\\KFY[ K[YF", + "G]RFR[ ILJLKMLQMSNTQUSUVTWSXQYMZL[L", + "H\\K[O[LTKPKLLINGQFSFVGXIYLYPXTU[Y[", + "G[G[IZLWOSSLVFV[UXSUQSNQLQKRKTLVNXQZT[Y[", + "F]SHTITLSPRSQUOXMZK[J[IZIWJRKOLMNJPHRGUFXFZG[I[KZMYNWOTP SPTPWQXRYTYWXYWZU[R[PZOX", + "H\\TLTMUNWNYMZKZIYGWFTFQGOIMLLNKRKVLYMZO[Q[TZVXWV", + "G^TFRGQIPMOSNVMXKZI[G[FZFXGWIWKXMZP[S[VZXXZT[O[KZHYGWFTFRHRJSMUPWRZT\\U", + "H\\VJVKWLYLZKZIYGVFRFOGNINLONPOSPPPMQLRKTKWLYMZP[S[VZXXYV", + "H\\RLPLNKMINGQFTFXG[G]F XGVNTTRXPZN[L[JZIXIVJULUNV QPZP", + "G^G[IZMVPQQNRJRGQFPFOGNINLONQOUOXNYMZKZQYVXXVZS[O[LZJXIVIT", + "F^MMKLJJJIKGMFNFPGQIQKPONULYJ[H[GZGX MRVOXN[L]J^H^G]F\\FZHXLVRUWUZV[W[YZZY\\V", + "IZWVUTSQROQLQIRGSFUFVGWIWLVQTVSXQZO[M[KZJXJVKUMUOV", + "JYT^R[PVOPOJPGRFTFUGVJVMURR[PaOdNfLgKfKdLaN^P\\SZWX", + "F^MMKLJJJIKGMFNFPGQIQKPONULYJ[H[GZGX ^I^G]F\\FZGXIVLTNROPO ROSQSXTZU[V[XZYY[V", + "I\\MRORSQVOXMYKYHXFVFUGTISNRSQVPXNZL[J[IZIXJWLWNXQZT[V[YZ[X", + "@aEMCLBJBICGEFFFHGIIIKHPGTE[ GTJLLHMGOFPFRGSISKRPQTO[ QTTLVHWGYFZF\\G]I]K\\PZWZZ[[\\[^Z_YaV", + "E]JMHLGJGIHGJFKFMGNINKMPLTJ[ LTOLQHRGTFVFXGYIYKXPVWVZW[X[ZZ[Y]V", + "H]TFQGOIMLLNKRKVLYMZO[Q[TZVXXUYSZOZKYHXGVFTFRHRKSNUQWSZU\\V", + "F_SHTITLSPRSQUOXMZK[J[IZIWJRKOLMNJPHRGUFZF\\G]H^J^M]O\\PZQWQUPTO", + "H^ULTNSOQPOPNNNLOIQGTFWFYGZIZMYPWSSWPYNZK[I[HZHXIWKWMXPZS[V[YZ[X", + "F_SHTITLSPRSQUOXMZK[J[IZIWJRKOLMNJPHRGUFYF[G\\H]J]M\\O[PYQVQSPTQUSUXVZX[ZZ[Y]V", + "H\\H[JZLXOTQQSMTJTGSFRFQGPIPKQMSOVQXSYUYWXYWZT[P[MZKXJVJT", + "H[RLPLNKMINGQFTFXG[G]F XGVNTTRXPZN[L[JZIXIVJULUNV", + "E]JMHLGJGIHGJFKFMGNINKMOLRKVKXLZN[P[RZSYUUXMZF XMWQVWVZW[X[ZZ[Y]V", + "F]KMILHJHIIGKFLFNGOIOKNOMRLVLYM[O[QZTWVTXPYMZIZGYFXFWGVIVKWNYP[Q", + "C_HMFLEJEIFGHFIFKGLILLK[ UFK[ UFS[ aF_G\\JYNVTS[", + "F^NLLLKKKILGNFPFRGSISLQUQXRZT[V[XZYXYVXUVU ]I]G\\FZFXGVITLPUNXLZJ[H[GZGX", + "F]KMILHJHIIGKFLFNGOIOKNOMRLVLXMZN[P[RZTXVUWSYM [FYMVWT]RbPfNgMfMdNaP^S[VY[V", + "H]ULTNSOQPOPNNNLOIQGTFWFYGZIZMYPWTTWPZN[K[JZJXKWNWPXQYR[R^QaPcNfLgKfKdLaN^Q[TYZV", + "", + "", + "", + "", + "", + "", + "I[JFR[ ZFR[ JFZF", + "G]IL[b", + "E_RJIZ RJ[Z", + "I[J[Z[", + "I[J[Z[ZZJZJ[", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "I\\XMX[ XPVNTMQMONMPLSLUMXOZQ[T[VZXX", + "H[LFL[ LPNNPMSMUNWPXSXUWXUZS[P[NZLX", + "I[XPVNTMQMONMPLSLUMXOZQ[T[VZXX", + "I\\XFX[ XPVNTMQMONMPLSLUMXOZQ[T[VZXX", + "I[LSXSXQWOVNTMQMONMPLSLUMXOZQ[T[VZXX", + "MYWFUFSGRJR[ OMVM", + "I\\XMX]W`VaTbQbOa XPVNTMQMONMPLSLUMXOZQ[T[VZXX", + "I\\MFM[ MQPNRMUMWNXQX[", + "NVQFRGSFREQF RMR[", + "MWRFSGTFSERF SMS^RaPbNb", + "IZMFM[ WMMW QSX[", + "NVRFR[", + "CaGMG[ GQJNLMOMQNRQR[ RQUNWMZM\\N]Q][", + "I\\MMM[ MQPNRMUMWNXQX[", + "I\\QMONMPLSLUMXOZQ[T[VZXXYUYSXPVNTMQM", + "H[LMLb LPNNPMSMUNWPXSXUWXUZS[P[NZLX", + "I\\XMXb XPVNTMQMONMPLSLUMXOZQ[T[VZXX", + "KXOMO[ OSPPRNTMWM", + "J[XPWNTMQMNNMPNRPSUTWUXWXXWZT[Q[NZMX", + "MYRFRWSZU[W[ OMVM", + "I\\MMMWNZP[S[UZXW XMX[", + "JZLMR[ XMR[", + "G]JMN[ RMN[ RMV[ ZMV[", + "J[MMX[ XMM[", + "JZLMR[ XMR[P_NaLbKb", + "J[XMM[ MMXM M[X[", + "H]QMONMPLRKUKXLZN[P[RZUWWTYPZM QMSMTNUPWXXZY[Z[", + "I\\UFSGQIOMNPMTLZKb UFWFYHYKXMWNUORO ROTPVRWTWWVYUZS[Q[OZNYMV", + "I\\JPLNNMOMQNROSRSVR[ ZMYPXRR[P_Ob", + "I[TMQMONMPLSLVMYNZP[R[TZVXWUWRVOTMRKQIQGRFTFVGXI", + "JZWOVNTMQMONOPPRSS SSOTMVMXNZP[S[UZWX", + "JYTFRGQHQIRJUKXK XKTMQONRMUMWNYP[S]T_TaSbQbP`", + "H\\IQJOLMNMONOPNTL[ NTPPRNTMVMXOXRWWTb", + "G\\HQIOKMMMNNNPMUMXNZO[Q[SZUWVUWRXMXJWGUFSFRHRJSMUPWRZT", + "LWRMPTOXOZP[R[TYUW", + "I[OMK[ YNXMWMUNQROSNS NSPTQUSZT[U[VZ", + "JZKFMFOGPHX[ RML[", + "H]OMIb NQMVMYO[Q[SZUXWT YMWTVXVZW[Y[[Y\\W", + "I[LMOMNSMXL[ YMXPWRUURXOZL[", + "JZTFRGQHQIRJUKXK UKRLPMOOOQQSTTVT TTPUNVMXMZO\\S^T_TaRbPb", + "J[RMPNNPMSMVNYOZQ[S[UZWXXUXRWOVNTMRM", + "G]PML[ UMVSWXX[ IPKNNM[M", + "I[MSMVNYOZQ[S[UZWXXUXRWOVNTMRMPNNPMSIb", + "I][MQMONMPLSLVMYNZP[R[TZVXWUWRVOUNSM", + "H\\SMP[ JPLNOMZM", + "H\\IQJOLMNMONOPMVMYO[Q[TZVXXTYPYM", + "G]ONMOKQJTJWKYLZN[Q[TZWXYUZRZOXMVMTORSPXMb", + "I[KMMMOOU`WbYb ZMYOWRM]K`Jb", + "F]VFNb GQHOJMLMMNMPLULXMZO[Q[TZVXXUZP[M", + "F]NMLNJQITIWJZK[M[OZQW RSQWRZS[U[WZYWZTZQYNXM", + "L\\UUTSRRPRNSMTLVLXMZO[Q[SZTXVRUWUZV[W[YZZY\\V", + "M[MVOSRNSLTITGSFQGPIOMNTNZO[P[RZTXUUURVVWWYW[V", + "MXTTTSSRQROSNTMVMXNZP[S[VYXV", + "L\\UUTSRRPRNSMTLVLXMZO[Q[SZTXZF VRUWUZV[W[YZZY\\V", + "NXOYQXRWSUSSRRQROSNUNXOZQ[S[UZVYXV", + "OWOVSQUNVLWIWGVFTGSIQQNZKaJdJfKgMfNcOZP[R[TZUYWV", + "L[UUTSRRPRNSMTLVLXMZO[Q[SZTY VRTYPdOfMgLfLdMaP^S\\U[XY[V", + "M\\MVOSRNSLTITGSFQGPIOMNSM[ M[NXOVQSSRURVSVUUXUZV[W[YZZY\\V", + "PWSMSNTNTMSM PVRRPXPZQ[R[TZUYWV", + "PWSMSNTNTMSM PVRRLdKfIgHfHdIaL^O\\Q[TYWV", + "M[MVOSRNSLTITGSFQGPIOMNSM[ M[NXOVQSSRURVSVUTVQV QVSWTZU[V[XZYY[V", + "OWOVQSTNULVIVGUFSGRIQMPTPZQ[R[TZUYWV", + "E^EVGSIRJSJTIXH[ IXJVLSNRPRQSQTPXO[ PXQVSSURWRXSXUWXWZX[Y[[Z\\Y^V", + "J\\JVLSNROSOTNXM[ NXOVQSSRURVSVUUXUZV[W[YZZY\\V", + "LZRRPRNSMTLVLXMZO[Q[SZTYUWUUTSRRQSQURWTXWXYWZV", + "KZKVMSNQMUGg MUNSPRRRTSUUUWTYSZQ[ MZO[R[UZWYZV", + "L[UUTSRRPRNSMTLVLXMZO[Q[SZ VRUUSZPaOdOfPgRfScS\\U[XY[V", + "MZMVOSPQPSSSTTTVSYSZT[U[WZXYZV", + "NYNVPSQQQSSVTXTZR[ NZP[T[VZWYYV", + "OXOVQSSO VFPXPZQ[S[UZVYXV PNWN", + "L[LVNRLXLZM[O[QZSXUU VRTXTZU[V[XZYY[V", + "L[LVNRMWMZN[O[RZTXUUUR URVVWWYW[V", + "I^LRJTIWIYJ[L[NZPX RRPXPZQ[S[UZWXXUXR XRYVZW\\W^V", + "JZJVLSNRPRQSQZR[U[XYZV WSVRTRSSOZN[L[KZ", + "L[LVNRLXLZM[O[QZSXUU VRPdOfMgLfLdMaP^S\\U[XY[V", + "LZLVNSPRRRTTTVSXQZN[P\\Q^QaPdOfMgLfLdMaP^S\\WYZV", + "J\\K[NZQXSVUSWOXKXIWGUFSGRHQJPOPTQXRZT[V[XZYY", + "", + "", + "", + "", + "", + "I[WUWRVOUNSMQMONMPLSLVMYNZP[R[TZVXWUXPXKWHVGTFRFPGNI", + "JZWNUMRMPNNPMSMVNYOZQ[T[VZ MTUT", + "J[TFRGPJOLNOMTMXNZO[Q[SZUWVUWRXMXIWGVFTF NPWP", + "H\\VFNb QMNNLPKSKVLXNZQ[S[VZXXYUYRXPVNSMQM", + "I[XOWNTMQMNNMOLQLSMUOWSZT\\T^S_Q_", + "", + "", + "DaWNVLTKQKOLNMMOMRNTOUQVTVVUWS WKWSXUYV[V\\U]S]O\\L[JYHWGTFQFNGLHJJILHOHRIUJWLYNZQ[T[WZYY", + "F^ZIJRZ[", + "F^JIZRJ[", + "KYOBOb OBVB ObVb", + "KYUBUb NBUB NbUb", + "KYTBQEPHPJQMSOSPORSTSUQWPZP\\Q_Tb", + "KYPBSETHTJSMQOQPURQTQUSWTZT\\S_Pb", + "F^[FYGVHSHPGNFLFJGIIIKKMMMOLPJPHNF [FI[ YTWTUUTWTYV[X[ZZ[X[VYT", + "NV", + "JZ", + "H\\QFNGLJKOKRLWNZQ[S[VZXWYRYOXJVGSFQF", + "H\\NJPISFS[", + "H\\LKLJMHNGPFTFVGWHXJXLWNUQK[Y[", + "H\\MFXFRNUNWOXPYSYUXXVZS[P[MZLYKW", + "H\\UFKTZT UFU[", + "H\\WFMFLOMNPMSMVNXPYSYUXXVZS[P[MZLYKW", + "H\\XIWGTFRFOGMJLOLTMXOZR[S[VZXXYUYTXQVOSNRNOOMQLT", + "H\\YFO[ KFYF", + "H\\PFMGLILKMMONSOVPXRYTYWXYWZT[P[MZLYKWKTLRNPQOUNWMXKXIWGTFPF", + "H\\XMWPURRSQSNRLPKMKLLINGQFRFUGWIXMXRWWUZR[P[MZLX", + "MWRYQZR[SZRY", + "MWSZR[QZRYSZS\\R^Q_", + "MWRMQNROSNRM RYQZR[SZRY", + "MWRMQNROSNRM SZR[QZRYSZS\\R^Q_", + "MWRFRT RYQZR[SZRY", + "I[LKLJMHNGPFTFVGWHXJXLWNVORQRT RYQZR[SZRY", + "NVRFRM", + "JZNFNM VFVM", + "KYQFOGNINKOMQNSNUMVKVIUGSFQF", + "H\\PBP_ TBT_ YIWGTFPFMGKIKKLMMNOOUQWRXSYUYXWZT[P[MZKX", + "G][BIb", + "KYVBTDRGPKOPOTPYR]T`Vb", + "KYNBPDRGTKUPUTTYR]P`Nb", + "NVRBRb", + "E_IR[R", + "E_RIR[ IR[R", + "E_IO[O IU[U", + "G]KKYY YKKY", + "JZRLRX MOWU WOMU", + "MWRQQRRSSRRQ", + "MWSFRGQIQKRLSKRJ", + "MWRHQGRFSGSIRKQL", + "E_UMXP[RXTUW IR[R", + "G]OFOb UFUb JQZQ JWZW", + "E_\\O\\N[MZMYNXPVUTXRZP[L[JZIYHWHUISJRQNRMSKSIRGPFNGMIMKNNPQUXWZY[[[\\Z\\Y", + "G]IIJKKOKUJYI[ [IZKYOYUZY[[ IIKJOKUKYJ[I I[KZOYUYYZ[[", + "F_\\Q[OYNWNUOTPQTPUNVLVJUISIQJOLNNNPOQPTTUUWVYV[U\\S\\Q", + "KYOBO[ UBU[", + "F^RBR[ I[[[", + "F^[BI[[[", + "E_RIQJRKSJRI IYHZI[JZIY [YZZ[[\\Z[Y", + "F^RHNLKPJSJUKWMXOXQWRU RHVLYPZSZUYWWXUXSWRU RUQYP\\ RUSYT\\ P\\T\\", + "F^RNQKPINHMHKIJKJOKRLTNWR\\ RNSKTIVHWHYIZKZOYRXTVWR\\", + "F^RGPJLOIR RGTJXO[R IRLUPZR] [RXUTZR]", + "F^RTTWVXXXZW[U[SZQXPVPSQ SQUOVMVKUISHQHOINKNMOOQQ QQNPLPJQISIUJWLXNXPWRT RTQYP\\ RTSYT\\ P\\T\\", + "F^RRR[Q\\ RVQ\\ RIQHOHNINKONRR RISHUHVIVKUNRR RRNOLNJNIOIQJR RRVOXNZN[O[QZR RRNULVJVIUISJR RRVUXVZV[U[SZR", + "F^ISJSLTMVMXLZ ISIRJQLQMRNTNWMYLZ RGPIOLOOQUQXPZR\\ RGTIULUOSUSXTZR\\ [S[RZQXQWRVTVWWYXZ [SZSXTWVWXXZ KVYV", + "", + "", + "", + "PSSRRSQSPRPQQPRPSQSSRUQV QQQRRRRQQQ", + "PTQPPQPSQTSTTSTQSPQP RQQRRSSRRQ", + "NVPOTU TOPU NRVR", + "MWRKQMOPMR RKSMUPWR RMOQ RMUQ ROPQ ROTQ QQSQ MRWR", + "MWMRMQNOONQMSMUNVOWQWR PNTN OOUO NPVP NQVQ MRWR", + "LRLFLRRRLF LIPQ LLOR LOMQ", + "MWRKQMOPMR RKSMUPWR", + "MWWRWQVOUNSMQMONNOMQMR", + "G]]R]P\\MZJWHTGPGMHJJHMGPGR", + "MWMRMSNUOVQWSWUVVUWSWR", + "LXLPNRQSSSVRXP", + "RURUTTURTPRO", + "RVRRUPVNVLUKTK", + "NRRROPNNNLOKPK", + "MWWHVGTFQFOGNHMJMLNNOOUSVTWVWXVZU[S\\P\\N[MZ", + "G]IWHVGTGQHOINKMMMONPOTUUVWWYW[V\\U]S]P\\N[M", + "G]RRTUUVWWYW[V\\U]S]Q\\O[NYMWMUNTOPUOVMWKWIVHUGSGQHOINKMMMONPORR", + "H\\KFK[ HF[FQP[Z ZV[Y\\[ ZVZY WYZY WYZZ\\[", + "KYUARBPCNELHKLKRLUNWQXSXVWXUYR KPLMNKQJSJVKXMYPYVXZV]T_R`Oa", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + ">f>RfR", + "D`D``D", + "RRR>Rf", + "D`DD``", + "D`DR`R", + "F^FY^K", + "KYK^YF", + "", + "KYKFY^", + "F^FK^Y", + "KYKRYR", + "MWMWWM", + "", + "MWMMWW", + "", + "", + "", + "", + "D`DOGQKSPTTTYS]Q`O", + "PUUDSGQKPPPTQYS]U`", + "OTODQGSKTPTTSYQ]O`", + "D`DUGSKQPPTPYQ]S`U", + "KYRJYNKVRZ", + "JZJRNKVYZR", + "KYKVKNYVYN", + "JZLXJPZTXL", + "JZJ]L]O\\Q[TXUVVSVOULTJSIQIPJOLNONSOVPXS[U\\X]Z]", + "I]]Z]X\\U[SXPVOSNONLOJPIQISJTLUOVSVVUXT[Q\\O]L]J", + "JZZGXGUHSIPLONNQNUOXPZQ[S[TZUXVUVQUNTLQIOHLGJG", + "G[GJGLHOIQLTNUQVUVXUZT[S[QZPXOUNQNNOLPISHUGXGZ", + "E[EPFRHTJUMVQVUUXSZP[NZLWLSMQNNPLSKVKYL\\M^", + "EYETHVKWPWSVVTXQYNYLXKVKSLPNNQMTMYN\\P_", + "OUQOOQOSQUSUUSUQSOQO QPPQPSQTSTTSTQSPQP RQQRRSSRRQ", + "", + "D`DRJR ORUR ZR`R", + "D`DUDO`O`U", + "JZRDJR RDZR", + "D`DR`R JYZY P`T`", + "D`DR`R DRRb `RRb", + "", + "", + "", + "", + "", + "KYQKNLLNKQKSLVNXQYSYVXXVYSYQXNVLSKQK", + "LXLLLXXXXLLL", + "KYRJKVYVRJ", + "LXRHLRR\\XRRH", + "JZRIPOJOOSMYRUWYUSZOTORI", + "KYRKRY KRYR", + "MWMMWW WMMW", + "MWRLRX MOWU WOMU", + "", + "", + "NVQNOONQNSOUQVSVUUVSVQUOSNQN OQOS PPPT QOQU RORU SOSU TPTT UQUS", + "NVNNNVVVVNNN OOOU POPU QOQU RORU SOSU TOTU UOUU", + "MWRLMUWURL ROOT ROUT RRQT RRST", + "LULRUWUMLR ORTU ORTO RRTS RRTQ", + "MWRXWOMORX RUUP RUOP RRSP RRQP", + "OXXROMOWXR URPO URPU RRPQ RRPS", + "LXRLNWXPLPVWRL RRRL RRLP RRNW RRVW RRXP", + "", + "", + "", + "MWRLRX OOUO MUOWQXSXUWWU", + "LXRLRX LQMOWOXQ PWTW", + "KYMNWX WNMX OLLOKQ ULXOYQ", + "I[NII[ VI[[ MM[[ WMI[ NIVI MMWM", + "I[RGRV MJWP WJMP IVL\\ [VX\\ IV[V L\\X\\", + "G[MJSV KPSL G\\[\\[RG\\", + "LXPLPPLPLTPTPXTXTTXTXPTPTLPL", + "KYYPXNVLSKQKNLLNKQKSLVNXQYSYVXXVYT YPWNUMSMQNPOOQOSPUQVSWUWWVYT", + "KYRJKVYVRJ RZYNKNRZ", + "G]PIPGQFSFTGTI GZHXJVKTLPLKMJOIUIWJXKXPYTZV\\X]Z GZ]Z QZP[Q\\S\\T[SZ", + "JZRMRS RSQ\\ RSS\\ Q\\S\\ RMQJPHNG QJNG RMSJTHVG SJVG RMNKLKJM PLLLJM RMVKXKZM TLXLZM RMPNOOOR RMPOOR RMTNUOUR RMTOUR", + "JZRIRK RNRP RSRU RYQ\\ RYS\\ Q\\S\\ RGQIPJ RGSITJ PJRITJ RKPNNOMN RKTNVOWN NOPORNTOVO RPPSNTLTKRKSLT RPTSVTXTYRYSXT NTPTRSTTVT RUPXOYMZLZKYJWJYLZ RUTXUYWZXZYYZWZYXZ MZOZRYUZWZ", + "JZRYQ\\ RYS\\ Q\\S\\ RYUZXZZXZUYTWTYRZOYMWLUMVJUHSGQGOHNJOMMLKMJOKRMTKTJUJXLZOZRY", + "JZRYQ\\ RYS\\ Q\\S\\ RYVXVVXUXRZQZLYIXHVHTGPGNHLHKIJLJQLRLUNVNXRY", + "I[IPKR LKNP RGRO XKVP [PYR", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "QSRQQRRSSRRQ", + "PTQPPQPSQTSTTSTQSPQP", + "NVQNOONQNSOUQVSVUUVSVQUOSNQN", + "MWQMONNOMQMSNUOVQWSWUVVUWSWQVOUNSMQM", + "KYQKNLLNKQKSLVNXQYSYVXXVYSYQXNVLSKQK", + "G]PGMHJJHMGPGTHWJZM\\P]T]W\\ZZ\\W]T]P\\MZJWHTGPG", + "AcPALBJCGEEGCJBLAPATBXCZE]G_JaLbPcTcXbZa]__]aZbXcTcPbLaJ_G]EZCXBTAPA", + "fRAPCMDJDGCEA>H@JAMAZB]D_G`M`PaRc RATCWDZD]C_AfHdJcMcZb]`_]`W`TaRc", + "AcRAPCMDJDGCEABGAKAPBTDXG\\L`Rc RATCWDZD]C_AbGcKcPbT`X]\\X`Rc BHbH", + "H[WPVQWRXQXPVNTMQMNNLPKSKULXNZQ[S[VZXX QMONMPLSLUMXOZQ[ LbXF", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "KYRKMX RNVX RKWX OTTT KXPX TXYX", + "JZNKNX OKOX LKSKVLWNVPSQ SKULVNUPSQ OQSQVRWTWUVWSXLX SQURVTVUUWSX", + "KYVLWKWOVLTKQKOLNMMPMSNVOWQXTXVWWU QKOMNPNSOVQX", + "JZNKNX OKOX LKSKVLWMXPXSWVVWSXLX SKULVMWPWSVVUWSX", + "JYNKNX OKOX SOSS LKVKVOUK OQSQ LXVXVTUX", + "JXNKNX OKOX SOSS LKVKVOUK OQSQ LXQX", + "K[VLWKWOVLTKQKOLNMMPMSNVOWQXTXVW QKOMNPNSOVQX TXUWVU VSVX WSWX TSYS", + "J[NKNX OKOX VKVX WKWX LKQK TKYK OQVQ LXQX TXYX", + "NWRKRX SKSX PKUK PXUX", + "LXSKSURWQX TKTUSWQXPXNWMUNTOUNV QKVK", + "JZNKNX OKOX WKOS QQVX RQWX LKQK TKYK LXQX TXYX", + "KXOKOX PKPX MKRK MXWXWTVX", + "I\\MKMX NNRX NKRU WKRX WKWX XKXX KKNK WKZK KXOX UXZX", + "JZNKNX OMVX OKVV VKVX LKOK TKXK LXPX", + "KZQKOLNMMPMSNVOWQXTXVWWVXSXPWMVLTKQK QKOMNPNSOVQX TXVVWSWPVMTK", + "JYNKNX OKOX LKSKVLWNWOVQSROR SKULVNVOUQSR LXQX", + "KZQKOLNMMPMSNVOWQXTXVWWVXSXPWMVLTKQK QKOMNPNSOVQX TXVVWSWPVMTK PWPUQTSTTUUZV[W[XZ TUUXVZW[", + "JZNKNX OKOX LKSKVLWNWOVQSROR SKULVNVOUQSR LXQX SRTSUWVXWXXW SRUSVWWX", + "KZVMWKWOVMULSKQKOLNMNOOPQQTRVSWT NNOOQPTQVRWSWVVWTXRXPWOVNTNXOV", + "KZRKRX SKSX NKMOMKXKXOWK PXUX", + "J[NKNUOWQXTXVWWUWK OKOUPWQX LKQK UKYK", + "KYMKRX NKRU WKRX KKPK TKYK", + "I[LKOX MKOT RKOX RKUX SKUT XKUX JKOK VKZK", + "KZNKVX OKWX WKNX LKQK TKYK LXQX TXYX", + "LYNKRRRX OKSR WKSRSX LKQK TKYK PXUX", + "LYVKNX WKOX OKNONKWK NXWXWTVX", + "KYRKMX RNVX RKWX OTTT KXPX TXYX", + "JZNKNX OKOX LKSKVLWNVPSQ SKULVNUPSQ OQSQVRWTWUVWSXLX SQURVTVUUWSX", + "KXOKOX PKPX MKWKWOVK MXRX", + "KYRKLX RMWX RKXX MWVW LXXX", + "JYNKNX OKOX SOSS LKVKVOUK OQSQ LXVXVTUX", + "LYVKNX WKOX OKNONKWK NXWXWTVX", + "J[NKNX OKOX VKVX WKWX LKQK TKYK OQVQ LXQX TXYX", + "KZQKOLNMMPMSNVOWQXTXVWWVXSXPWMVLTKQK QKOMNPNSOVQX TXVVWSWPVMTK QOQT TOTT QQTQ QRTR", + "NWRKRX SKSX PKUK PXUX", + "JZNKNX OKOX WKOS QQVX RQWX LKQK TKYK LXQX TXYX", + "KYRKMX RNVX RKWX KXPX TXYX", + "I\\MKMX NNRX NKRU WKRX WKWX XKXX KKNK WKZK KXOX UXZX", + "JZNKNX OMVX OKVV VKVX LKOK TKXK LXPX", + "JZMJLM XJWM PPOS UPTS MVLY XVWY MKWK MLWL PQTQ PRTR MWWW MXWX", + "KZQKOLNMMPMSNVOWQXTXVWWVXSXPWMVLTKQK QKOMNPNSOVQX TXVVWSWPVMTK", + "J[NKNX OKOX VKVX WKWX LKYK LXQX TXYX", + "JYNKNX OKOX LKSKVLWNWOVQSROR SKULVNVOUQSR LXQX", + "K[MKRQ NKSQMX MKWKXOVK NWWW MXWXXTVX", + "KZRKRX SKSX NKMOMKXKXOWK PXUX", + "KZMONLOKPKQLRORX XOWLVKUKTLSOSX MONMOLPLQMRO XOWMVLULTMSO PXUX", + "KZRKRX SKSX QNNOMQMRNTQUTUWTXRXQWOTNQN QNOONQNROTQU TUVTWRWQVOTN PKUK PXUX", + "KZNKVX OKWX WKNX LKQK TKYK LXQX TXYX", + "J[RKRX SKSX LPMONOOSQU TUVSWOXOYP MONROTQUTUVTWRXO PKUK PXUX", + "KZMVNXQXMRMONMOLQKTKVLWMXOXRTXWXXV OUNRNOOMQK TKVMWOWRVU NWPW UWWW", + "KYTKKX SMTX TKUX NTTT IXNX RXWX", + "JYPKLX QKMX NKUKWLWNVPSQ UKVLVNUPSQ OQRQTRUSUUTWQXJX RQTSTUSWQX", + "KXVLWLXKWNVLTKRKPLOMNOMRMUNWPXRXTWUU RKPMOONRNVPX", + "JYPKLX QKMX NKTKVLWNWQVTUVTWQXJX TKULVNVQUTTVSWQX", + "JYPKLX QKMX SORS NKXKWNWK OQRQ JXTXUUSX", + "JXPKLX QKMX SORS NKXKWNWK OQRQ JXOX", + "KYVLWLXKWNVLTKRKPLOMNOMRMUNWPXRXTWUVVS RKPMOONRNVPX RXTVUS SSXS", + "J[PKLX QKMX XKTX YKUX NKSK VK[K OQVQ JXOX RXWX", + "NWTKPX UKQX RKWK NXSX", + "LXUKRUQWPX VKSURWPXOXMWLUMTNUMV SKXK", + "JZPKLX QKMX YKOR RPTX SPUX NKSK VK[K JXOX RXWX", + "KXQKMX RKNX OKTK KXUXVUTX", + "I\\OKKX OMPX PKQV YKPX YKUX ZKVX MKPK YK\\K IXMX SXXX", + "JZPKLX PKTX QKTU XKTX NKQK VKZK JXNX", + "KYRKPLOMNOMRMUNWPXRXTWUVVTWQWNVLTKRK RKPMOONRNVPX RXTVUTVQVMTK", + "JYPKLX QKMX NKUKWLXMXOWQTROR UKWMWOVQTR JXOX", + "KYRKPLOMNOMRMUNWPXRXTWUVVTWQWNVLTKRK RKPMOONRNVPX RXTVUTVQVMTK OWOVPUQURVRZS[T[UZ RVSZT[", + "JZPKLX QKMX NKUKWLXMXOWQTROR UKWMWOVQTR SRTWUXVXWW SRTSUWVX JXOX", + "KZWLXLYKXNWLUKRKPLOMOOPPUSVT ONPOURVSVVUWSXPXNWMULXMWNW", + "KZTKPX UKQX PKNNOKZKYNYK NXSX", + "J[PKMUMWOXSXUWVUYK QKNUNWOX NKSK WK[K", + "KYOKPX PKQV YKPX MKRK VK[K", + "I[NKMX OKNV TKMX TKSX UKTV ZKSX LKQK XK\\K", + "KZPKTX QKUX YKLX NKSK VK[K JXOX RXWX", + "LYPKRQPX QKSQ YKSQQX NKSK VK[K NXSX", + "LYXKLX YKMX QKONPKYK LXUXVUTX", + "", + "", + "", + "", + "", + "", + "", + "KZMHX\\", + "JZRMLW RMXW", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "LZQOPPPQOQOPQOTOVQVWWXXX TOUQUWWX URRSPTOUOWPXSXTWUU RSPUPWQX", + "JYNKNX OKOX ORPPROTOVPWRWUVWTXRXPWOU TOUPVRVUUWTX LKOK", + "LXVQUQURVRVQUPSOQOOPNRNUOWQXSXUWVV QOPPOROUPWQX", + "L[VKVX WKWX VRUPSOQOOPNRNUOWQXSXUWVU QOPPOROUPWQX TKWK VXYX", + "LXOSVSVRUPSOQOOPNRNUOWQXSXUWVV USUQSO QOPPOROUPWQX", + "LWTKULUMVMVLTKRKPMPX RKQMQX NOSO NXSX", + "LYQOOQOSQUSUUSUQSOQO QOPQPSQU SUTSTQSO TPUOVO PTOUOXPYTYVZ OWPXTXVYV[T\\P\\N[NYPX", + "J[NKNX OKOX ORPPROTOVPWRWX TOUPVRVX LKOK LXQX TXYX", + "NWRKRLSLSKRK RORX SOSX POSO PXUX", + "NWSKSLTLTKSK SOSZR\\ TOTZR\\P\\O[OZPZP[O[ QOTO", + "JZNKNX OKOX WOOU RSVX SSWX LKOK TOYO LXQX TXYX", + "NWRKRX SKSX PKSK PXUX", + "F_JOJX KOKX KRLPNOPORPSRSX POQPRRRX SRTPVOXOZP[R[X XOYPZRZX HOKO HXMX PXUX XX]X", + "J[NONX OOOX ORPPROTOVPWRWX TOUPVRVX LOOO LXQX TXYX", + "LYQOOPNRNUOWQXTXVWWUWRVPTOQO QOPPOROUPWQX TXUWVUVRUPTO", + "JYNON\\ OOO\\ ORPPROTOVPWRWUVWTXRXPWOU TOUPVRVUUWTX LOOO L\\Q\\", + "KYUOU\\ VOV\\ URTPROPONPMRMUNWPXRXTWUU POOPNRNUOWPX S\\X\\", + "KXOOOX POPX PRQPSOUOVPVQUQUPVP MOPO MXRX", + "LYTOUPUQVQVPTOQOOPORQSTTVU OQQRTSVTVWTXQXOWOVPVPWQX", + "LWPKPVRXTXUWUV QKQVRX NOTO", + "J[NONUOWQXSXUWVU OOOUPWQX VOVX WOWX LOOO TOWO VXYX", + "KYNORX OORV VORX LOQO TOXO", + "I[LOOX MOOU ROOX ROUX SOUU XOUX JOOO VOZO", + "KYNOUX OOVX VONX LOQO TOXO LXPX SXXX", + "KYNORX OORV VORXP[N\\M\\L[LZMZM[L[ LOQO TOXO", + "LXUONX VOOX OONQNOVO NXVXVVUX", + "K[QOOPNQMSMUNWPXQXSWUUWRXO QOOQNSNUOWPX QOSOUPWWXX SOTPVWXXYX", + "KXRKPMOOMUK\\ QLPNNTL\\ RKTKVLVNUPRQ TKULUNTPRQ RQTRUTUVTWRXQXOWNT RQSRTTTVRX", + "KYLQNOPORPSSSXR\\ LQNPPPRQSS WOVRSXQ\\", + "KYSOQOOPNQMSMUNWPXRXTWUVVTVRUPRNQLQKRJTJUKVM QOOQNSNVPX RXTVUTUQSO QLRKTKVM", + "LXVPTOQOOPOQPRRS QOPPPQRS RSOTNUNWPXSXUW RSPTOUOWPX", + "LWRKQLQMSNVNVMSNPOOPNRNTOVPWRXSYS[R\\P\\O[ SNQOPPOROTPVRX", + "IYJRKPLONOOPOQMX MONPNQLX OQPPROTOVPVRS\\ TOUPURR\\", + "IYJSKQLPNPOQOVPX MPNQNUOWPXQXSWTVUTVQVNULTKRKQLQNRPURWS QXSVTTUQUNTK", + "NWROPVPWQXSXUWVU SOQVQWRX", + "KYOOLX POMX UOVPWPVOTORQOR ORPSRWTXVWWU ORQSSWTX", + "LXLKNKPLWX NKOLVX RPMX RPNX", + "KZOOK\\ POL\\ NUNWOXQXSWTV VOTVTWUXWXXWYU WOUVUWVX", + "JYNOMX OONUMX VRVOWOVRTUQWNXMX LOOO", + "MXRKQLQMSNVN TNQOPPPRRSUS TNROQPQRRS SSPTOUOWQXSYTZT[S\\Q\\ SSQTPUPWQX", + "KXQOOPNQMSMUNWPXRXTWUVVTVRUPSOQO QOOQNSNVPX RXTVUTUQSO", + "IZPPMX PPNX TPSX TPTX KQMOXO KQMPXP", + "JXSOQOOPNQMSJ\\ QOOQNSK\\ SOUPVRVTUVTWRXPXNWMU SOUQUTTVRX", + "K[YOQOOPNQMSMUNWPXRXTWUVVTVRUPYP QOOQNSNVPX RXTVUTUQSO", + "KZSPQX SPRX MQOOXO MQOPXP", + "JXKRLPMOOOPPPROUOWPX NOOPORNUNWPXQXSWUUVRVOUOVP", + "KZOPNQMSMUNWPXRXUWWUXRXPWOUOTPSRRUO\\ MUNVPWRWUVWTXR XQWPUPSR RUQXP\\", + "KXMONOPPS[T\\ NOOPR[T\\U\\ VOTRNYL\\", + "I[TKQ\\ UKP\\ JRKPLONOOPOVPWSWUVWT MONPNTOWPXSXUWWTXRYO", + "JZNPPPPONPMQLSLUMWNXPXQWRUSR LUNWPWRU RRRWSXUXWVXTXRWPVOVPWP RUSWUWWV", + "KZVOTVTWUXWXXWYU WOUVUWVX USUQSOQOOPNQMSMUNWPXRXTV QOOQNSNVPX", + "JXOKMR PKNRNVPX NROPQOSOUPVRVTUVTWRXPXNWMUMR SOUQUTTVRX MKPK", + "KXUPUQVQUPSOQOOPNQMSMUNWPXRXTWUV QOOQNSNVPX", + "KZWKTVTWUXWXXWYU XKUVUWVX USUQSOQOOPNQMSMUNWPXRXTV QOOQNSNVPX UKXK", + "KWNURTTSURUPSOQOOPNQMSMUNWPXRXTWUV QOOQNSNVPX", + "MXWKXLXKVKTLSNPYO[N\\ VKULTNQYP[N\\L\\L[M\\ POVO", + "KYVOTVSYR[ WOUVTYR[P\\M\\L[M[N\\ USUQSOQOOPNQMSMUNWPXRXTV QOOQNSNVPX", + "KZPKLX QKMX OQPPROTOVPVRUUUWVX TOUPURTUTWUXWXXWYU NKQK", + "MWSKSLTLTKSK NROPPOROSPSRRURWSX QORPRRQUQWRXTXUWVU", + "MWTKTLULUKTK ORPPQOSOTPTRRYQ[O\\M\\M[N\\ ROSPSRQYP[O\\", + "KXPKLX QKMX VPUQVQVPUOTORQPROR ORPSQWRXTXUWVU ORQSRWSX NKQK", + "NVSKPVPWQXSXTWUU TKQVQWRX QKTK", + "F^GRHPIOKOLPLQJX JOKPKQIX LQMPOOQOSPSQQX QORPRQPX SQTPVOXOZPZRYUYWZX XOYPYRXUXWYX[X\\W]U", + "J[KRLPMOOOPPPQNX NOOPOQMX PQQPSOUOWPWRVUVWWX UOVPVRUUUWVXXXYWZU", + "KXQOOPNQMSMUNWPXRXTWUVVTVRUPSOQO QOOQNSNVPX RXTVUTUQSO", + "JYKRLPMOOOPPPQM\\ NOOPOQL\\ PQROTOVPWRWTVVUWSXQXOVOT TOVQVTUVSX J\\O\\", + "KYVOR\\ WOS\\ USUQSOQOOPNQMSMUNWPXRXTV QOOQNSNVPX P\\U\\", + "LXMRNPOOQORPRQPX POQPQQOX RQSPUOVOWPWQVQWP", + "LYVPVQWQVPTOQOOPORQSTTVU OQQRTSVTVWTXQXOWNVOVOW", + "NWSKPVPWQXSXTWUU TKQVQWRX POUO", + "IZJRKPLONOOPORNUNWOX MONPNRMUMWOXQXSWTV VOTVTWUXWXXWYU WOUVUWVX", + "JXKRLPMOOOPPPROUOWPX NOOPORNUNWPXQXSWUUVRVOUOVP", + "H\\IRJPKOMONPNRMUMWNX LOMPMRLULWNXOXQWRV TORVRWTX UOSVSWTXUXWWYUZRZOYOZP", + "JZMRNPPOROSPSR QORPRRQUPWNXMXLWLVMVLW XPWQXQXPWOVOTPSRRURWSX QUQWRXTXVWWU", + "IYJRKPLONOOPORNUNWOX MONPNRMUMWOXQXSWTV VOTVSYR[ WOUVTYR[P\\M\\L[M[N\\", + "KYWOWPVQNVMWMX NQOOROUQ OPRPUQVQ NVOVRWUW OVRXUXVV", + "H[RKSLSMTMTLRKOKMLLNLX OKNLMNMX XKYLYMZMZLXKVKTMTX VKUMUX JOWO JXOX RXWX", + "J[UKVLWLWKQKOLNNNX QKPLONOX VOVX WOWX LOWO LXQX TXYX", + "J[WKQKOLNNNX QKPLONOX UKVLVX WKWX LOVO LXQX TXYX", + "F_PKQLQMRMRLPKMKKLJNJX MKLLKNKX YKZL[L[KUKSLRNRX UKTLSNSX ZOZX [O[X HO[O HXMX PXUX XX]X", + "F_PKQLQMRMRLPKMKKLJNJX MKLLKNKX [KUKSLRNRX UKTLSNSX YKZLZX [K[X HOZO HXMX PXUX XX]X", + "NWRORX SOSX POSO PXUX", + "", + "LXVPTOROPPOQNSNUOWQXSXUW ROPQOSOVQX OSSS", + "LYSKQLPMOONRNUOWPXRXTWUVVTWQWNVLUKSK SKQMPOOSOVPX RXTVUTVPVMUK OQVQ", + "KZTKQ\\ UKP\\ QONPMRMUNWQXTXWWXUXRWPTOQO QOOPNRNUOWQX TXVWWUWRVPTO", + "LXUPVRVQUPSOQOOPNRNTOVRX QOOQOTPVRXSYS[R\\P\\", + "", + "", + "", + "I[VKWLXLVKSKQLPMOOLYK[J\\ SKQMPOMYL[J\\H\\H[I\\ ZK[L[KYKWLVNSYR[Q\\ YKXLWNTYS[Q\\O\\O[P\\ LOYO", + "IZVKWLXLXKSKQLPMOOLYK[J\\ SKQMPOMYL[J\\H\\H[I\\ VOTVTWUXWXXWYU WOUVUWVX LOWO", + "IZVKWL XKSKQLPMOOLYK[J\\ SKQMPOMYL[J\\H\\H[I\\ WKTVTWUXWXXWYU XKUVUWVX LOVO", + "F^SKTLTM ULSKPKNLMMLOIYH[G\\ PKNMMOJYI[G\\E\\E[F\\ ZK[L\\L\\KWKUL TMSOPYO[N\\ WKUMTOQYP[N\\L\\L[M\\ ZOXVXWYX[X\\W]U [OYVYWZX IO[O", + "F^SKTLTM ULSKPKNLMMLOIYH[G\\ PKNMMOJYI[G\\E\\E[F\\ ZK[L \\KWKUL TMSOPYO[N\\ WKUMTOQYP[N\\L\\L[M\\ [KXVXWYX[X\\W]U \\KYVYWZX IOZO", + "MWNROPPOROSPSRRURWSX QORPRRQUQWRXTXUWVU", + "", + "OU", + "LX", + "LYQKOLNONTOWQXTXVWWTWOVLTKQK QKPLOOOTPWQX TXUWVTVOULTK", + "LYPNSKSX RLRX OXVX", + "LYOMONNNNMOLQKTKVLWNVPTQQROSNUNX TKULVNUPTQ NWOVPVSWVWWV PVSXVXWVWU", + "LYOMONNNNMOLQKTKVLWNVPTQ TKULVNUPTQ RQTQVRWTWUVWTXQXOWNVNUOUOV TQURVTVUUWTX", + "LYSMSX TKTX TKMTXT QXVX", + "LYOKNQ OKVK OLSLVK NQOPQOTOVPWRWUVWTXQXOWNVNUOUOV TOUPVRVUUWTX", + "LYVMVNWNWMVLTKRKPLOMNPNUOWQXTXVWWUWSVQTPQPNR RKPMOPOUPWQX TXUWVUVSUQTP", + "LYNKNO VMRTPX WKTQQX NMPKRKUM NMPLRLUMVM", + "LYQKOLNNOPQQTQVPWNVLTKQK QKPLONPPQQ TQUPVNULTK QQORNTNUOWQXTXVWWUWTVRTQ QQPROTOUPWQX TXUWVUVTURTQ", + "LYOVOUNUNVOWQXSXUWVVWSWNVLTKQKOLNNNPORQSTSWQ SXUVVSVNULTK QKPLONOPPRQS", + "NVRVQWRXSWRV", + "NVSWRXQWRVSWSYQ[", + "NVROQPRQSPRO RVQWRXSWRV", + "NVROQPRQSPRO SWRXQWRVSWSYQ[", + "NVRKQLRSSLRK RLRO RVQWRXSWRV", + "LYNNONOONONNOLQKTKVLWNWOVQSRRSRTST TKVMVPUQSR RWRXSXSWRW", + "OVRKRP SKRP", + "LXOKOP PKOP UKUP VKUP", + "MWQKPLPNQOSOTNTLSKQK", + "MWRJRP OKUO UKOO", + "KZXHM\\", + "MWUHSJQMPPPTQWSZU\\ SJRLQPQTRXSZ", + "MWOHQJSMTPTTSWQZO\\ QJRLSPSTRXQZ", + "MWPHP\\ QHQ\\ PHUH P\\U\\", + "MWSHS\\ THT\\ OHTH O\\T\\", + "LWSHRIQKQMRORPPRRTRUQWQYR[S\\ RIQM QKRO RUQY QWR[", + "MXQHRISKSMRORPTRRTRUSWSYR[Q\\ RISM SKRO RUSY SWR[", + "MWTHPRT\\", + "MWPHTRP\\", + "OURHR\\", + "MWPHP\\ THT\\", + "I[LRXR", + "I[RLRX LRXR", + "JZRMRX MRWR MXWX", + "JZRMRX MMWM MRWR", + "JZMMWW WMMW", + "NVRQQRRSSRRQ", + "I[RLQMRNSMRL LRXR RVQWRXSWRV", + "I[LPXP LTXT", + "I[WLMX LPXP LTXT", + "I[LNXN LRXR LVXV", + "JZWLMRWX", + "JZMLWRMX", + "JZWKMOWS MTWT MXWX", + "JZMKWOMS MTWT MXWX", + "H[YUWUUTTSRPQOONNNLOKQKRLTNUOUQTRSTPUOWNYN", + "JZLTLRMPOPUSWSXR LRMQOQUTWTXRXP", + "JZMSRPWS MSRQWS", + "NVSKPO SKTLPO", + "NVQKTO QKPLTO", + "LXNKOMQNSNUMVK NKONQOSOUNVK", + "NVSLRMQLRKSLSNQP", + "NVSKQMQORPSORNQO", + "NVQLRMSLRKQLQNSP", + "NVQKSMSORPQORNSO", + "", + "JZWMQMONNOMQMSNUOVQWWW", + "JZMMMSNUOVQWSWUVVUWSWM", + "JZMMSMUNVOWQWSVUUVSWMW", + "JZMWMQNOONQMSMUNVOWQWW", + "JZWMQMONNOMQMSNUOVQWWW MRUR", + "I[TOUPXRUTTU UPWRUT LRWR", + "MWRMRX OPPORLTOUP PORMTO", + "I[POOPLROTPU OPMROT MRXR", + "MWRLRW OTPURXTUUT PURWTU", + "KYVSUPSOQOOPNQMSMUNWPXRXTWUVVTWQWNVLTKQKPLQLRK QOOQNSNVPX RXTVUTVQVNULTK", + "JZLKRX MKRV XKRX LKXK NLWL", + "G[IOLORW KORX [FRX", + "I[XIXJYJYIXHVHTJSLROQUPYO[ UITKSORUQXPZN\\L\\K[KZLZL[", + "I[XIXJYJYIXHVHTJSLROQUPYO[ UITKSORUQXPZN\\L\\K[KZLZL[ QNOONQNSOUQVSVUUVSVQUOSNQN", + "H\\ZRYTWUVUTTSSQPPONNMNKOJQJRKTMUNUPTQSSPTOVNWNYOZQZR", + "JZXKLX OKPLPNOOMOLNLLMKOKSLVLXK UTTUTWUXWXXWXUWTUT", + "J[YPXPXQYQYPXOWOVPUTTVSWQXOXMWLVLTMSORRPSNSLRKPKOLONPQUWWXXXYW OXMVMTOR ONPPVWWX", + "J[UPSOQOPQPRQTSTUS UOUSVTXTYRYQXNVLSKRKOLMNLQLRMUOWRXSXVW", + "KZQHQ\\ THT\\ WLVLVMWMWLUKPKNLNNOPVSWT NNOOVRWTWVVWTXQXOWNVNUOUOVNV", + "KYPKP[ TKT[ MQWQ MUWU", + "LXTLSLSMTMTLSKQKPLPNQPTRUS PNQOTQUSUUSW QPOROTPVSXTY OTPUSWTYT[S\\Q\\P[PZQZQ[P[", + "LXRKQLRMSLRK RMRQ RQQSRVSSRQ RVR\\ POONNOOPPOTOUNVOUPTO", + "LXRMSLRKQLRMRQQRSURV RQSRQURVRZQ[R\\S[RZ POONNOOPPOTOUNVOUPTO PXOWNXOYPXTXUWVXUYTX", + "LYVKVX NKVK QQVQ NXVX", + "", + "H\\QKNLLNKQKSLVNXQYSYVXXVYSYQXNVLSKQK RQQRRSSRRQ", + "LYQKPLPMQN TKULUMTN RNPOOQORPTRUSUUTVRVQUOSNRN RURY SUSY OWVW", + "LYRKPLONOOPQRRSRUQVOVNULSKRK RRRX SRSX OUVU", + "H\\QKNLLNKQKSLVNXQYSYVXXVYSYQXNVLSKQK RKRY KRYR", + "JYRRPQOQMRLTLUMWOXPXRWSUSTRR WMRR RMWMWR RMVNWR", + "JZLLMKOKQLRNRPQRPSNT OKPLQNQQPS VKUX WKTX NTXT", + "JYNKNU OKNR NROPQOSOUPVQVTTVTXUYVYWX SOUQUTTV LKOK", + "LYONRKRQ VNSKSQ RQPROTOUPWRXSXUWVUVTURSQ RTRUSUSTRT", + "JZRKRY MKMPNRPSTSVRWPWK LMMKNM QMRKSM VMWKXM OVUV", + "JYNKNX OKOX LKSKVLWNWOVQSROR SKULVNVOUQSR LXVXVUUX", + "LYWKTKQLONNQNSOVQXTYWY WKTLRNQQQSRVTXWY", + "JZRRPQOQMRLTLUMWOXPXRWSUSTRR SLQQ WMRR XQSS", + "KYPMTW TMPW MPWT WPMT", + "J[OUMULVLXMYOYPXPVNTMRMONMOLQKTKVLWMXOXRWTUVUXVYXYYXYVXUVU NMPLULWM", + "J[OOMOLNLLMKOKPLPNNPMRMUNWOXQYTYVXWWXUXRWPUNULVKXKYLYNXOVO NWPXUXWW", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "F^KHK\\ LHL\\ XHX\\ YHY\\ HH\\H H\\O\\ U\\\\\\", + "H]KHRQJ\\ JHQQ JHYHZMXH K[X[ J\\Y\\ZWX\\", + "KYVBTDRGPKOPOTPYR]T`Vb TDRHQKPPPTQYR\\T`", + "KYNBPDRGTKUPUTTYR]P`Nb PDRHSKTPTTSYR\\P`", + "KYOBOb PBPb OBVB ObVb", + "KYTBTb UBUb NBUB NbUb", + "JYTBQEPHPJQMSOSPORSTSUQWPZP\\Q_Tb RDQGQKRN RVQYQ]R`", + "KZPBSETHTJSMQOQPURQTQUSWTZT\\S_Pb RDSGSKRN RVSYS]R`", + "KYU@RCPFOIOLPOSVTYT\\S_Ra RCQEPHPKQNTUUXU[T^RaOd", + "KYO@RCTFUIULTOQVPYP\\Q_Ra RCSETHTKSNPUOXO[P^RaUd", + "AXCRGRR` GSRa FSRb X:Rb", + "F^[CZD[E\\D\\C[BYBWCUETGSJRNPZO^N` VDUFTJRVQZP]O_MaKbIbHaH`I_J`Ia", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "H\\RFK[ RFY[ RIX[ MUVU I[O[ U[[[", + "G]LFL[ MFM[ IFUFXGYHZJZLYNXOUP UFWGXHYJYLXNWOUP MPUPXQYRZTZWYYXZU[I[ UPWQXRYTYWXYWZU[", + "G\\XIYLYFXIVGSFQFNGLIKKJNJSKVLXNZQ[S[VZXXYV QFOGMILKKNKSLVMXOZQ[", + "G]LFL[ MFM[ IFSFVGXIYKZNZSYVXXVZS[I[ SFUGWIXKYNYSXVWXUZS[", + "G\\LFL[ MFM[ SLST IFYFYLXF MPSP I[Y[YUX[", + "G[LFL[ MFM[ SLST IFYFYLXF MPSP I[P[", + "G^XIYLYFXIVGSFQFNGLIKKJNJSKVLXNZQ[S[VZXX QFOGMILKKNKSLVMXOZQ[ XSX[ YSY[ US\\S", + "F^KFK[ LFL[ XFX[ YFY[ HFOF UF\\F LPXP H[O[ U[\\[", + "MXRFR[ SFS[ OFVF O[V[", + "KZUFUWTZR[P[NZMXMVNUOVNW TFTWSZR[ QFXF", + "F\\KFK[ LFL[ YFLS QOY[ POX[ HFOF UF[F H[O[ U[[[", + "I[NFN[ OFO[ KFRF K[Z[ZUY[", + "F_KFK[ LFRX KFR[ YFR[ YFY[ ZFZ[ HFLF YF]F H[N[ V[][", + "G^LFL[ MFYY MHY[ YFY[ IFMF VF\\F I[O[", + "G]QFNGLIKKJOJRKVLXNZQ[S[VZXXYVZRZOYKXIVGSFQF QFOGMILKKOKRLVMXOZQ[ S[UZWXXVYRYOXKWIUGSF", + "G]LFL[ MFM[ IFUFXGYHZJZMYOXPUQMQ UFWGXHYJYMXOWPUQ I[P[", + "G]QFNGLIKKJOJRKVLXNZQ[S[VZXXYVZRZOYKXIVGSFQF QFOGMILKKOKRLVMXOZQ[ S[UZWXXVYRYOXKWIUGSF NYNXOVQURUTVUXV_W`Y`Z^Z] UXV\\W^X_Y_Z^", + "G]LFL[ MFM[ IFUFXGYHZJZLYNXOUPMP UFWGXHYJYLXNWOUP I[P[ RPTQURXYYZZZ[Y TQUSWZX[Z[[Y[X", + "H\\XIYFYLXIVGSFPFMGKIKKLMMNOOUQWRYT KKMMONUPWQXRYTYXWZT[Q[NZLXKUK[LX", + "I\\RFR[ SFS[ LFKLKFZFZLYF O[V[", + "F^KFKULXNZQ[S[VZXXYUYF LFLUMXOZQ[ HFOF VF\\F", + "H\\KFR[ LFRX YFR[ IFOF UF[F", + "F^JFN[ KFNV RFN[ RFV[ SFVV ZFV[ GFNF WF]F", + "H\\KFX[ LFY[ YFK[ IFOF UF[F I[O[ U[[[", + "H]KFRQR[ LFSQS[ ZFSQ IFOF VF\\F O[V[", + "H\\XFK[ YFL[ LFKLKFYF K[Y[YUX[", + "H\\RFK[ RFY[ RIX[ MUVU I[O[ U[[[", + "G]LFL[ MFM[ IFUFXGYHZJZLYNXOUP UFWGXHYJYLXNWOUP MPUPXQYRZTZWYYXZU[I[ UPWQXRYTYWXYWZU[", + "I[NFN[ OFO[ KFZFZLYF K[R[", + "H\\RFJ[ RFZ[ RIY[ KZYZ J[Z[", + "G\\LFL[ MFM[ SLST IFYFYLXF MPSP I[Y[YUX[", + "H\\XFK[ YFL[ LFKLKFYF K[Y[YUX[", + "F^KFK[ LFL[ XFX[ YFY[ HFOF UF\\F LPXP H[O[ U[\\[", + "G]QFNGLIKKJOJRKVLXNZQ[S[VZXXYVZRZOYKXIVGSFQF QFOGMILKKOKRLVMXOZQ[ S[UZWXXVYRYOXKWIUGSF OMOT UMUT OPUP OQUQ", + "MXRFR[ SFS[ OFVF O[V[", + "F\\KFK[ LFL[ YFLS QOY[ POX[ HFOF UF[F H[O[ U[[[", + "H\\RFK[ RFY[ RIX[ I[O[ U[[[", + "F_KFK[ LFRX KFR[ YFR[ YFY[ ZFZ[ HFLF YF]F H[N[ V[][", + "G^LFL[ MFYY MHY[ YFY[ IFMF VF\\F I[O[", + "G]KEJJ ZEYJ ONNS VNUS KWJ\\ ZWY\\ KGYG KHYH OPUP OQUQ KYYY KZYZ", + "G]QFNGLIKKJOJRKVLXNZQ[S[VZXXYVZRZOYKXIVGSFQF QFOGMILKKOKRLVMXOZQ[ S[UZWXXVYRYOXKWIUGSF", + "F^KFK[ LFL[ XFX[ YFY[ HF\\F H[O[ U[\\[", + "G]LFL[ MFM[ IFUFXGYHZJZMYOXPUQMQ UFWGXHYJYMXOWPUQ I[P[", + "H]KFRPJ[ JFQP JFYFZLXF KZXZ J[Y[ZUX[", + "I\\RFR[ SFS[ LFKLKFZFZLYF O[V[", + "I\\KKKILGMFOFPGQIRMR[ KIMGOGQI ZKZIYGXFVFUGTISMS[ ZIXGVGTI O[V[", + "H]RFR[ SFS[ PKMLLMKOKRLTMUPVUVXUYTZRZOYMXLUKPK PKNLMMLOLRMTNUPV UVWUXTYRYOXMWLUK OFVF O[V[", + "H\\KFX[ LFY[ YFK[ IFOF UF[F I[O[ U[[[", + "G^RFR[ SFS[ IMJLLMMQNSOTQU JLKMLQMSNTQUTUWTXSYQZM[L TUVTWSXQYM[L\\M OFVF O[V[", + "G]JXK[O[MWKSJPJLKIMGPFTFWGYIZLZPYSWWU[Y[ZX MWLTKPKLLINGPF TFVGXIYLYPXTWW KZNZ VZYZ", + "H\\UFH[ UFV[ THU[ LUUU F[L[ R[X[", + "F^OFI[ PFJ[ LFWFZG[I[KZNYOVP WFYGZIZKYNXOVP MPVPXQYSYUXXVZR[F[ VPWQXSXUWXUZR[", + "H]ZH[H\\F[L[JZHYGWFTFQGOIMLLOKSKVLYMZP[S[UZWXXV TFRGPINLMOLSLVMYNZP[", + "F]OFI[ PFJ[ LFUFXGYHZKZOYSWWUYSZO[F[ UFWGXHYKYOXSVWTYRZO[", + "F]OFI[ PFJ[ TLRT LF[FZLZF MPSP F[U[WVT[", + "F\\OFI[ PFJ[ TLRT LF[FZLZF MPSP F[M[", + "H^ZH[H\\F[L[JZHYGWFTFQGOIMLLOKSKVLYMZP[R[UZWXYT TFRGPINLMOLSLVMYNZP[ R[TZVXXT UT\\T", + "E_NFH[ OFI[ [FU[ \\FV[ KFRF XF_F LPXP E[L[ R[Y[", + "LYUFO[ VFP[ RFYF L[S[", + "I[XFSWRYQZO[M[KZJXJVKULVKW WFRWQYO[ TF[F", + "F]OFI[ PFJ[ ]FLS SOW[ ROV[ LFSF YF_F F[M[ S[Y[", + "H\\QFK[ RFL[ NFUF H[W[YUV[", + "E`NFH[ NFO[ OFPY \\FO[ \\FV[ ]FW[ KFOF \\F`F E[K[ S[Z[", + "F_OFI[ OFVX OIV[ \\FV[ LFOF YF_F F[L[", + "G]SFPGNILLKOJSJVKYLZN[Q[TZVXXUYRZNZKYHXGVFSF SFQGOIMLLOKSKVLYN[ Q[SZUXWUXRYNYKXHVF", + "F]OFI[ PFJ[ LFXF[G\\I\\K[NYPUQMQ XFZG[I[KZNXPUQ F[M[", + "G]SFPGNILLKOJSJVKYLZN[Q[TZVXXUYRZNZKYHXGVFSF SFQGOIMLLOKSKVLYN[ Q[SZUXWUXRYNYKXHVF LYLXMVOUPURVSXS_T`V`W^W] SXT^U_V_W^", + "F^OFI[ PFJ[ LFWFZG[I[KZNYOVPMP WFYGZIZKYNXOVP RPTQURVZW[Y[ZYZX URWYXZYZZY F[M[", + "G^ZH[H\\F[L[JZHYGVFRFOGMIMKNMONVRXT MKOMVQWRXTXWWYVZS[O[LZKYJWJUI[JYKY", + "H]UFO[ VFP[ OFLLNF]F\\L\\F L[S[", + "F_NFKQJUJXKZN[R[UZWXXU\\F OFLQKUKXLZN[ KFRF YF_F", + "H\\NFO[ OFPY \\FO[ LFRF XF^F", + "E_MFK[ NFLY UFK[ UFS[ VFTY ]FS[ JFQF ZF`F", + "G]NFU[ OFV[ \\FH[ LFRF XF^F F[L[ R[X[", + "H]NFRPO[ OFSPP[ ]FSP LFRF YF_F L[S[", + "G][FH[ \\FI[ OFLLNF\\F H[V[XUU[", + "H\\KILKXWYYY[ LLXX KIKKLMXYY[ PPLTKVKXLZK[ KVMZ LTLVMXMZK[ SSXN VIVLWNYNYLWKVI VIWLYN", + "H\\QIK[ SIY[ RIX[ MUVU I[O[ U[[[ QBOCNENGOIQJSJUIVGVEUCSBQB", + "", + "", + "", + "", + "", + "G]IB[b", + "F^RJIZ RJ[Z", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "I]NONPMPMONNPMTMVNWOXQXXYZZ[ WOWXXZZ[[[ WQVRPSMTLVLXMZP[S[UZWX PSNTMVMXNZP[", + "G\\LFL[ MFM[ MPONQMSMVNXPYSYUXXVZS[Q[OZMX SMUNWPXSXUWXUZS[ IFMF", + "H[WPVQWRXQXPVNTMQMNNLPKSKULXNZQ[S[VZXX QMONMPLSLUMXOZQ[", + "H]WFW[ XFX[ WPUNSMQMNNLPKSKULXNZQ[S[UZWX QMONMPLSLUMXOZQ[ TFXF W[[[", + "H[LSXSXQWOVNTMQMNNLPKSKULXNZQ[S[VZXX WSWPVN QMONMPLSLUMXOZQ[", + "KXUGTHUIVHVGUFSFQGPIP[ SFRGQIQ[ MMUM M[T[", + "I\\QMONNOMQMSNUOVQWSWUVVUWSWQVOUNSMQM ONNPNTOV UVVTVPUN VOWNYMYNWN NUMVLXLYM[P\\U\\X]Y^ LYMZP[U[X\\Y^Y_XaUbObLaK_K^L\\O[", + "G]LFL[ MFM[ MPONRMTMWNXPX[ TMVNWPW[ IFMF I[P[ T[[[", + "MXRFQGRHSGRF RMR[ SMS[ OMSM O[V[", + "MXSFRGSHTGSF TMT_SaQbObNaN`O_P`Oa SMS_RaQb PMTM", + "G\\LFL[ MFM[ WMMW RSX[ QSW[ IFMF TMZM I[P[ T[Z[", + "MXRFR[ SFS[ OFSF O[V[", + "BcGMG[ HMH[ HPJNMMOMRNSPS[ OMQNRPR[ SPUNXMZM]N^P^[ ZM\\N]P][ DMHM D[K[ O[V[ Z[a[", + "G]LML[ MMM[ MPONRMTMWNXPX[ TMVNWPW[ IMMM I[P[ T[[[", + "H\\QMNNLPKSKULXNZQ[S[VZXXYUYSXPVNSMQM QMONMPLSLUMXOZQ[ S[UZWXXUXSWPUNSM", + "G\\LMLb MMMb MPONQMSMVNXPYSYUXXVZS[Q[OZMX SMUNWPXSXUWXUZS[ IMMM IbPb", + "H\\WMWb XMXb WPUNSMQMNNLPKSKULXNZQ[S[UZWX QMONMPLSLUMXOZQ[ Tb[b", + "IZNMN[ OMO[ OSPPRNTMWMXNXOWPVOWN KMOM K[R[", + "J[WOXMXQWOVNTMPMNNMOMQNRPSUUWVXW MPNQPRUTWUXVXYWZU[Q[OZNYMWM[NY", + "KZPFPWQZS[U[WZXX QFQWRZS[ MMUM", + "G]LMLXMZP[R[UZWX MMMXNZP[ WMW[ XMX[ IMMM TMXM W[[[", + "I[LMR[ MMRY XMR[ JMPM TMZM", + "F^JMN[ KMNX RMN[ RMV[ SMVX ZMV[ GMNM WM]M", + "H\\LMW[ MMX[ XML[ JMPM TMZM J[P[ T[Z[", + "H[LMR[ MMRY XMR[P_NaLbKbJaK`La JMPM TMZM", + "I[WML[ XMM[ MMLQLMXM L[X[XWW[", + "G^QMNNLPKRJUJXKZN[P[RZUWWTYPZM QMONMPLRKUKXLZN[ QMSMUNVPXXYZZ[ SMTNUPWXXZZ[[[", + "G\\TFQGOIMMLPKTJZIb TFRGPINMMPLTKZJb TFVFXGYHYKXMWNTOPO VFXHXKWMVNTO POTPVRWTWWVYUZR[P[NZMYLV POSPURVTVWUYTZR[", + "H\\IPKNMMOMQNROSRSVRZOb JOLNPNRO ZMYPXRSYP^Nb YMXPWRSY", + "I\\VNTMRMONMQLTLWMYNZP[R[UZWWXTXQWOSJRHRFSEUEWFYH RMPNNQMTMXNZ R[TZVWWTWPVNTKSISGTFVFYH", + "I[XPVNTMPMNNNPPRSS PMONOPQRSS SSNTLVLXMZP[S[UZWX SSOTMVMXNZP[", + "I[TFRGQHQIRJUKZKZJWKSMPOMRLULWMYP[S]T_TaSbQbPa ULQONRMUMWNYP[", + "G]HQIOKMNMONOPNTL[ MMNNNPMTK[ NTPPRNTMVMXNYOYRXWUb VMXOXRWWTb", + "F]GQHOJMMMNNNPMUMXNZO[ LMMNMPLULXMZO[Q[SZUXWUXRYMYIXGVFTFRHRJSMUPWRZT SZUWVUWRXMXIWGVF", + "LXRMPTOXOZP[S[UYVW SMQTPXPZQ[", + "H\\NMJ[ OMK[ XMYNZNYMWMUNQROSMS OSQTSZT[ OSPTRZS[U[WZYW", + "H\\KFMFOGPHQJWXXZY[ MFOHPJVXWZY[Z[ RMJ[ RMK[", + "F]MMGb NMHb MPLVLYN[P[RZTXVU XMUXUZV[Y[[Y\\W YMVXVZW[", + "H\\NML[ OMNSMXL[ YMXQVU ZMYPXRVUTWQYOZL[ KMOM", + "IZTFRGQHQIRJUKXK UKQLOMNONQPSSTVT UKRLPMOOOQQSST STOUMVLXLZN\\S^T_TaRbPb STPUNVMXMZO\\S^", + "I[RMONMQLTLWMYNZP[R[UZWWXTXQWOVNTMRM RMPNNQMTMXNZ R[TZVWWTWPVN", + "G]PNL[ PNM[ VNV[ VNW[ IPKNNM[M IPKONN[N", + "H[LVMYNZP[R[UZWWXTXQWOVNTMRMONMQLTHb R[TZVWWTWPVN RMPNNQMTIb", + "H][MQMNNLQKTKWLYMZO[Q[TZVWWTWQVOUNSM QMONMQLTLXMZ Q[SZUWVTVPUN UN[N", + "H\\SNP[ SNQ[ JPLNOMZM JPLOONZN", + "H\\IQJOLMOMPNPPNVNYP[ NMONOPMVMYNZP[Q[TZVXXUYRYOXMWNXOYR XUYO", + "G]ONMOKQJTJWKYLZN[Q[TZWXYUZRZOXMVMTORSPXMb JWLYNZQZTYWWYU ZOXNVNTPRSPYNb", + "I[KMMMONPPU_VaWb MMNNOPT_UaWbYb ZMYOWRM]K`Jb", + "F]UFOb VFNb GQHOJMMMNNNPMUMXOZRZTYWVYS LMMNMPLULXMZO[R[TZVXXUYS[M", + "F]JQLOONNMLNJQITIWJZK[M[OZQWRT IWJYKZMZOYQW QTQWRZS[U[WZYWZTZQYNXMWNYOZQ QWRYSZUZWYYW", + "H]XMVTUXUZV[Y[[Y\\W YMWTVXVZW[ VTVQUNSMQMNNLQKTKWLYMZO[Q[SZUWVT QMONMQLTLXMZ", + "H[PFLSLVMYNZ QFMS MSNPPNRMTMVNWOXQXTWWUZR[P[NZMWMS VNWPWTVWTZR[ MFQF", + "I[WPWQXQXPWNUMRMONMQLTLWMYNZP[R[UZWW RMPNNQMTMXNZ", + "H]ZFVTUXUZV[Y[[Y\\W [FWTVXVZW[ VTVQUNSMQMNNLQKTKWLYMZO[Q[SZUWVT QMONMQLTLXMZ WF[F", + "I[MVQUTTWRXPWNUMRMONMQLTLWMYNZP[R[UZWX RMPNNQMTMXNZ", + "KZZGYHZI[H[GZFXFVGUHTJSMP[O_Na XFVHUJTNRWQ[P^O`NaLbJbIaI`J_K`Ja OMYM", + "H\\YMU[T^RaObLbJaI`I_J^K_J` XMT[S^QaOb VTVQUNSMQMNNLQKTKWLYMZO[Q[SZUWVT QMONMQLTLXMZ", + "H]PFJ[ QFK[ MTOPQNSMUMWNXOXQVWVZW[ UMWOWQUWUZV[Y[[Y\\W MFQF", + "LYUFTGUHVGUF MQNOPMSMTNTQRWRZS[ RMSNSQQWQZR[U[WYXW", + "LYVFUGVHWGVF NQOOQMTMUNUQR[Q^P`OaMbKbJaJ`K_L`Ka SMTNTQQ[P^O`Mb", + "H\\PFJ[ QFK[ XNWOXPYOYNXMWMUNQROSMS OSQTSZT[ OSPTRZS[U[WZYW MFQF", + "MYUFQTPXPZQ[T[VYWW VFRTQXQZR[ RFVF", + "AbBQCOEMHMINIPHTF[ GMHNHPGTE[ HTJPLNNMPMRNSOSQP[ PMRORQO[ RTTPVNXMZM\\N]O]Q[W[Z\\[ ZM\\O\\QZWZZ[[^[`YaW", + "F]GQHOJMMMNNNPMTK[ LMMNMPLTJ[ MTOPQNSMUMWNXOXQVWVZW[ UMWOWQUWUZV[Y[[Y\\W", + "I[RMONMQLTLWMYNZP[R[UZWWXTXQWOVNTMRM RMPNNQMTMXNZ R[TZVWWTWPVN", + "G\\HQIOKMNMONOPNTJb MMNNNPMTIb NTOQQNSMUMWNXOYQYTXWVZS[Q[OZNWNT WNXPXTWWUZS[ FbMb", + "H\\XMRb YMSb VTVQUNSMQMNNLQKTKWLYMZO[Q[SZUWVT QMONMQLTLXMZ ObVb", + "IZJQKOMMPMQNQPPTN[ OMPNPPOTM[ PTRPTNVMXMYNYOXPWOXN", + "J[XOXPYPYOXNUMRMONNONQORVVWW NPOQVUWVWYVZS[P[MZLYLXMXMY", + "KYTFPTOXOZP[S[UYVW UFQTPXPZQ[ NMWM", + "F]GQHOJMMMNNNQLWLYN[ LMMNMQKWKYLZN[P[RZTXVT XMVTUXUZV[Y[[Y\\W YMWTVXVZW[", + "H\\IQJOLMOMPNPQNWNYP[ NMONOQMWMYNZP[Q[TZVXXUYQYMXMYO", + "C`DQEOGMJMKNKQIWIYK[ IMJNJQHWHYIZK[M[OZQXRV TMRVRYSZU[W[YZ[X\\V]R]M\\M]O UMSVSYU[", + "H\\KQMNOMRMSOSR QMRORRQVPXNZL[K[JZJYKXLYKZ QVQYR[U[WZYW YNXOYPZOZNYMXMVNTPSRRVRYS[", + "G\\HQIOKMNMONOQMWMYO[ MMNNNQLWLYMZO[Q[SZUXWT ZMV[U^SaPbMbKaJ`J_K^L_K` YMU[T^RaPb", + "H\\YMXOVQNWLYK[ LQMOOMRMVO MOONRNVOXO LYNYRZUZWY NYR[U[WYXW", + "G^VGUHVIWHWGUFRFOGMILLL[ RFPGNIMLM[ \\G[H\\I]H]G\\FZFXGWIW[ ZFYGXIX[ IM[M I[P[ T[[[", + "G]WGVHWIXHWGUFRFOGMILLL[ RFPGNIMLM[ WMW[ XMX[ IMXM I[P[ T[[[", + "G]VGUHVIWHWGUF XFRFOGMILLL[ RFPGNIMLM[ WHW[ XFX[ IMWM I[P[ T[[[", + "BcRGQHRISHRGPFMFJGHIGLG[ MFKGIIHLH[ ]G\\H]I^H]G[FXFUGSIRLR[ XFVGTISLS[ ]M][ ^M^[ DM^M D[K[ O[V[ Z[a[", + "BcRGQHRISHRGPFMFJGHIGLG[ MFKGIIHLH[ \\G[H\\I]H]G[F ^FXFUGSIRLR[ XFVGTISLS[ ]H][ ^F^[ DM]M D[K[ O[V[ Z[a[", + "MXRMR[ SMS[ OMSM O[V[", + "", + "IZWNUMRMONMPLSLVMYNZQ[T[VZ RMPNNPMSMVNYOZQ[ MTUT", + "I\\TFQGOJNLMOLTLXMZO[Q[TZVWWUXRYMYIXGVFTF TFRGPJOLNOMTMXNZO[ Q[SZUWVUWRXMXIWGVF NPWP", + "G]UFOb VFNb QMMNKPJSJVKXMZP[S[WZYXZUZRYPWNTMQM QMNNLPKSKVLXNZP[ S[VZXXYUYRXPVNTM", + "I[TMVNXPXOWNTMQMNNMOLQLSMUOWSZ QMONNOMQMSNUSZT\\T^S_Q_", + "", + "", + "G]LMKNJPJRKUOYP[ JRKTOXP[P]O`MbLbKaJ_J\\KXMTOQRNTMVMYNZPZTYXWZU[T[SZSXTWUXTY VMXNYPYTXXWZ", + "E_YGXHYIZHYGWFTFQGOINKMNLRJ[I_Ha TFRGPIOKNNLWK[J^I`HaFbDbCaC`D_E`Da _G^H_I`H`G_F]F[GZHYJXMU[T_Sa ]F[HZJYNWWV[U^T`SaQbObNaN`O_P`Oa IM^M", + "F^[GZH[I\\H[GXFUFRGPIOKNNMRK[J_Ia UFSGQIPKONMWL[K^J`IaGbEbDaD`E_F`Ea YMWTVXVZW[Z[\\Y]W ZMXTWXWZX[ JMZM", + "F^YGXHYIZHZGXF \\FUFRGPIOKNNMRK[J_Ia UFSGQIPKONMWL[K^J`IaGbEbDaD`E_F`Ea [FWTVXVZW[Z[\\Y]W \\FXTWXWZX[ JMYM", + "@cTGSHTIUHTGRFOFLGJIIKHNGRE[D_Ca OFMGKIJKINGWF[E^D`CaAb?b>a>`?_@`?a `G_H`IaH`G]FZFWGUITKSNRRP[O_Na ZFXGVIUKTNRWQ[P^O`NaLbJbIaI`J_K`Ja ^M\\T[X[Z\\[_[aYbW _M]T\\X\\Z][ DM_M", + "@cTGSHTIUHTGRFOFLGJIIKHNGRE[D_Ca OFMGKIJKINGWF[E^D`CaAb?b>a>`?_@`?a ^G]H^I_H_G]F aFZFWGUITKSNRRP[O_Na ZFXGVIUKTNRWQ[P^O`NaLbJbIaI`J_K`Ja `F\\T[X[Z\\[_[aYbW aF]T\\X\\Z][ DM^M", + "LYMQNOPMSMTNTQRWRZS[ RMSNSQQWQZR[U[WYXW", + "", + "NV", + "JZ", + "H\\QFNGLJKOKRLWNZQ[S[VZXWYRYOXJVGSFQF QFOGNHMJLOLRMWNYOZQ[ S[UZVYWWXRXOWJVHUGSF", + "H\\NJPISFS[ RGR[ N[W[", + "H\\LJMKLLKKKJLHMGPFTFWGXHYJYLXNUPPRNSLUKXK[ TFVGWHXJXLWNTPPR KYLXNXSZVZXYYX NXS[W[XZYXYV", + "H\\LJMKLLKKKJLHMGPFTFWGXIXLWNTOQO TFVGWIWLVNTO TOVPXRYTYWXYWZT[P[MZLYKWKVLUMVLW WQXTXWWYVZT[", + "H\\THT[ UFU[ UFJUZU Q[X[", + "H\\MFKP KPMNPMSMVNXPYSYUXXVZS[P[MZLYKWKVLUMVLW SMUNWPXSXUWXUZS[ MFWF MGRGWF", + "H\\WIVJWKXJXIWGUFRFOGMILKKOKULXNZQ[S[VZXXYUYTXQVOSNRNOOMQLT RFPGNIMKLOLUMXOZQ[ S[UZWXXUXTWQUOSN", + "H\\KFKL KJLHNFPFUIWIXHYF LHNGPGUI YFYIXLTQSSRVR[ XLSQRSQVQ[", + "H\\PFMGLILLMNPOTOWNXLXIWGTFPF PFNGMIMLNNPO TOVNWLWIVGTF POMPLQKSKWLYMZP[T[WZXYYWYSXQWPTO PONPMQLSLWMYNZP[ T[VZWYXWXSWQVPTO", + "H\\XMWPURRSQSNRLPKMKLLINGQFSFVGXIYLYRXVWXUZR[O[MZLXLWMVNWMX QSORMPLMLLMIOGQF SFUGWIXLXRWVVXTZR[", + "MWRYQZR[SZRY", + "MWR[QZRYSZS\\R^Q_", + "MWRMQNROSNRM RYQZR[SZRY", + "MWRMQNROSNRM R[QZRYSZS\\R^Q_", + "MWRFQHRTSHRF RHRN RYQZR[SZRY", + "I[MJNKMLLKLJMHNGPFSFVGWHXJXLWNVORQRT SFUGVHWJWLVNTP RYQZR[SZRY", + "NVRFQM SFQM", + "JZNFMM OFMM VFUM WFUM", + "KYQFOGNINKOMQNSNUMVKVIUGSFQF", + "JZRFRR MIWO WIMO", + "G][BIb", + "KYVBTDRGPKOPOTPYR]T`Vb TDRHQKPPPTQYR\\T`", + "KYNBPDRGTKUPUTTYR]P`Nb PDRHSKTPTTSYR\\P`", + "KYOBOb PBPb OBVB ObVb", + "KYTBTb UBUb NBUB NbUb", + "JYTBQEPHPJQMSOSPORSTSUQWPZP\\Q_Tb RDQGQKRN RVQYQ]R`", + "KZPBSETHTJSMQOQPURQTQUSWTZT\\S_Pb RDSGSKRN RVSYS]R`", + "KYUBNRUb", + "KYOBVROb", + "NVRBRb", + "KYOBOb UBUb", + "E_IR[R", + "E_RIR[ IR[R", + "F^RJR[ JRZR J[Z[", + "F^RJR[ JJZJ JRZR", + "G]KKYY YKKY", + "MWQQQSSSSQQQ RQRS QRSR", + "E_RIQJRKSJRI IR[R RYQZR[SZRY", + "E_IO[O IU[U", + "E_YIK[ IO[O IU[U", + "E_IM[M IR[R IW[W", + "F^ZIJRZ[", + "F^JIZRJ[", + "F^ZFJMZT JVZV J[Z[", + "F^JFZMJT JVZV J[Z[", + "F_[WYWWVUTRPQOONMNKOJQJSKUMVOVQURTUPWNYM[M", + "F^IUISJPLONOPPTSVTXTZS[Q ISJQLPNPPQTTVUXUZT[Q[O", + "G]JTROZT JTRPZT", + "LXTFOL TFUGOL", + "LXPFUL PFOGUL", + "H\\KFLHNJQKSKVJXHYF KFLINKQLSLVKXIYF", + "MWRHQGRFSGSIRKQL", + "MWSFRGQIQKRLSKRJ", + "MWRHSGRFQGQIRKSL", + "MWQFRGSISKRLQKRJ", + "E[HMLMRY KMR[ [BR[", + "F^ZJSJOKMLKNJQJSKVMXOYSZZZ", + "F^JJJQKULWNYQZSZVYXWYUZQZJ", + "F^JJQJUKWLYNZQZSYVWXUYQZJZ", + "F^JZJSKOLMNKQJSJVKXMYOZSZZ", + "F^ZJSJOKMLKNJQJSKVMXOYSZZZ JRVR", + "E_XP[RXT UMZRUW IRZR", + "JZPLRITL MORJWO RJR[", + "E_LPIRLT OMJROW JR[R", + "JZPXR[TX MURZWU RIRZ", + "I\\XRWOVNTMRMONMQLTLWMYNZP[R[UZWXXUYPYKXHWGUFRFPGOHOIPIPH RMPNNQMTMXNZ R[TZVXWUXPXKWHUF", + "H\\JFR[ KFRY ZFR[ JFZF KGYG", + "AbDMIMRY HNR[ b:R[", + "F^[CZD[E\\D\\C[BYBWCUETGSJRNPZO^N` VDUFTJRVQZP]O_MaKbIbHaH`I_J`Ia", + "F^[CZD[E\\D\\C[BYBWCUETGSJRNPZO^N` VDUFTJRVQZP]O_MaKbIbHaH`I_J`Ia QKNLLNKQKSLVNXQYSYVXXVYSYQXNVLSKQK", + "F_\\S[UYVWVUUTTQPPONNLNJOIQISJULVNVPUQTTPUOWNYN[O\\Q\\S", + "F^[FI[ NFPHPJOLMMKMIKIIJGLFNFPGSHVHYG[F WTUUTWTYV[X[ZZ[X[VYTWT", + "F_[NZO[P\\O\\N[MZMYNXPVUTXRZP[M[JZIXIUJSPORMSKSIRGPFNGMIMKNNPQUXWZZ[[[\\Z\\Y M[KZJXJUKSMQ MKNMVXXZZ[", + "E`WNVLTKQKOLNMMPMSNUPVSVUUVS QKOMNPNSOUPV WKVSVUXVZV\\T]Q]O\\L[JYHWGTFQFNGLHJJILHOHRIUJWLYNZQ[T[WZYYZX XKWSWUXV", + "H\\PBP_ TBT_ XIWJXKYJYIWGTFPFMGKIKKLMMNOOUQWRYT KKMMONUPWQXRYTYXWZT[P[MZKXKWLVMWLX", + "G]OFOb UFUb JQZQ JWZW", + "JZUITJUKVJVIUGSFQFOGNINKOMQOVR OMTPVRWTWVVXTZ PNNPMRMTNVPXU[ NVSYU[V]V_UaSbQbOaN_N^O]P^O_", + "JZRFQHRJSHRF RFRb RQQTRbSTRQ LMNNPMNLLM LMXM TMVNXMVLTM", + "JZRFQHRJSHRF RFRT RPQRSVRXQVSRRP RTRb R^Q`RbS`R^ LMNNPMNLLM LMXM TMVNXMVLTM L[N\\P[NZL[ L[X[ T[V\\X[VZT[", + "I\\XFX[ KFXF PPXP K[X[", + "", + "E`QFNGKIILHOHRIUKXNZQ[T[WZZX\\U]R]O\\LZIWGTFQF ROQPQQRRSRTQTPSORO RPRQSQSPRP", + "J[PFNGOIQJ PFOGOI UFWGVITJ UFVGVI QJOKNLMNMQNSOTQUTUVTWSXQXNWLVKTJQJ RUR[ SUS[ NXWX", + "I\\RFOGMILLLMMPORRSSSVRXPYMYLXIVGSFRF RSR[ SSS[ NWWW", + "D`PFMGJIHLGOGSHVJYM[P\\T\\W[ZY\\V]S]O\\LZIWGTFPF RFR\\ GQ]Q", + "G`PMMNKPJSJTKWMYPZQZTYVWWTWSVPTNQMPM ]GWG[HUN ]G]M\\IVO \\HVN", + "F\\IIJGLFOFQGRIRLQOPQNSKU OFPGQIQMPPNS VFT[ WFS[ KUYU", + "I\\MFMU NFMQ MQNOONQMTMWNXPXRWTUV TMVNWPWRTXTZU[W[YY KFNF", + "I\\RNOOMQLTLUMXOZR[S[VZXXYUYTXQVOSNRN RHNJRFRN SHWJSFSN RSQTQURVSVTUTTSSRS RTRUSUSTRT", + "G^QHRFR[ THSFS[ JHKFKMLPNRQSRS MHLFLNMQ [HZFZMYPWRTSSS XHYFYNXQ NWWW", + "G]LFL[ MFM[ IFUFXGYHZJZMYOXPUQMQ UFWGXHYJYMXOWPUQ I[Y[YVX[", + "H[YGUGQHNJLMKPKSLVNYQ[U\\Y\\ YGVHSJQMPPPSQVSYV[Y\\", + "F_OQMQKRJSIUIWJYKZM[O[QZRYSWSURSQROQ SHPQ ZJRR \\QST", + "H\\OKUY UKOY KOYU YOKU", + "F^NVLUKUIVHXHYI[K\\L\\N[OYOXNVKRJOJMKJMHPGTGWHYJZMZOYRVVUXUYV[X\\Y\\[[\\Y\\X[VYUXUVV JMKKMIPHTHWIYKZM", + "F^NMLNKNIMHKHJIHKGLGNHOJOKNMKQJTJVKYM[P\\T\\W[YYZVZTYQVMUKUJVHXGYG[H\\J\\K[MYNXNVM JVKXMZP[T[WZYXZV", + "I[KYYK QLULYKXOXS ULXLXO", + "I[YKKY LQLUKYOXSX LULXOX", + "I[YYKK SLOLKKLOLS OLLLLO", + "I[KKYY QXUXYYXUXQ UXXXXU", + "", + "F_JMILIJJHLGNGPHQIRKSP IJKHMHOIPJQLRPR[ [M\\L\\J[HYGWGUHTISKRP \\JZHXHVIUJTLSPS[", + "F^IGJKKMMOPPTPWOYMZK[G IGJJKLMNPOTOWNYLZJ[G PONPMQLSLVMXOZQ[S[UZWXXVXSWQVPTO PPNQMSMVNY VYWVWSVQTP", + "F^MJMV NKNU VKVU WJWV IGKIMJPKTKWJYI[G IYKWMVPUTUWVYW[Y", + "F^[ILIJJILINJPLQNQPPQNQLPJ[J IMJOKPMQ QMPKOJMI IXXXZW[U[SZQXPVPTQSSSUTWIW [TZRYQWP STTVUWWX", + "F]OUMTLTJUIWIXJZL[M[OZPXPWOUJPINIKJILHOGSGWHYJZLZOYRVUUWUYV[X[YZZX MSKPJNJKKILH SGVHXJYLYOXRVU", + "G_HKKHMKMV JILLLV MKPHRKRU OIQLQU RKUHWKW[ TIVLV[ WKZH[J\\M\\P[SZUXWUYP[ YIZJ[M[PZSYUWWTYP[", + "F^ISMSLRKOKMLJNHQGSGVHXJYMYOXRWS[S ITOTMRLOLMMJOHQG SGUHWJXMXOWRUT[T KXYX KYYY", + "F_GLJIMLMX IJLMLX MLPISLSX OJRMRX SLVIYLYW[Y UJXMXXZZ]W", + "G]ZIJY ZIWJQJ XKUKQJ ZIYLYR XKXNYR QRJR PSMSJR QRQY PSPVQY", + "F^HOJKOU JMOWRPWPZO[M[KZIXHWHUITKTMUPVRWUWXUZ WHVIUKUMWQXTXWWYUZ", + "F^IOLLPN KMOORLUN QMTOWLYN VMXO[L IULRPT KSOURRUT QSTUWRYT VSXU[R", + "F^JHNJPLQOQRPUNWJY JHMIOJQLRO RRQUOWMXJY ZHWIUJSLRO RRSUUWWXZY ZHVJTLSOSRTUVWZY IP[P IQ[Q", + "", + "", + "", + "", + "NVQQQSSSSQQQ QQSS SQQS", + "JZMPQRTTVVWYW[V]U^ MQST MRPSTUVWWY", + "JZWKVMTOPQMR SPMS UFVGWIWKVNTPQRMT", + "H\\SMONLPKRKTLVNWQWUVXTYRYPXNVMSM XNSM VMQNLP ONKR LVQW NWSVXT UVYR", + "H\\SMONLPKRKTLVNWQWUVXTYRYPXNVMSM XNSM VMQNLP ONKR LVQW NWSVXT UVYR", + "J[SMPNNPMRMTNVPWRWUVWTXRXPWNUMSM OPUM NRVN MTWO NUXP OVWR PWVT", + "JZOGO^ UFU] MNWL MOWM MWWU MXWV", + "JZNFNX VLV^ NNVL NOVM NWVU NXVV", + "JZNBNW NNQLTLVMWOWQVSSUQVNW NNQMTMVN UMVOVQUSSU", + "E_HIHL \\I\\L HI\\I HJ\\J HK\\K HL\\L", + "JZMNMQ WNWQ MNWN MOWO MPWP MQWQ", + "JZMLWX MLONQOTOVNWMWKUKUMTO ONTO QOWM VKVN ULWL WXUVSUPUNVMWMYOYOWPU UVPU SUMW NVNY MXOX", + "JZPOOMOKMKMMNNPOSOUNWL NKNN MLOL MMSO POUN WLWY", + "A^GfHfIeIdHcGcFdFfGhIiKiNhPfQdR`RUQ;Q4R/S-U,V,X-Y/Y3X6W8U;P?JCHEFHEJDNDREVGYJ[N\\R\\V[XZZW[T[PZMYKWITHPHMIKKJNJRKUMW GdGeHeHdGd U;Q?LCIFGIFKENERFVGXJ[ R\\U[WZYWZTZPYMXKVITH", + "EfNSOUQVSVUUVSVQUOSNQNOONPMSMVNYP[S\\V\\Y[[Y\\W]T]P\\MZJXIUHRHOIMJKLIOHSHXI]KaMcPeTfYf]e`cba KLJNIRIXJ\\L`NbQdUeYe]d_cba POTO OPUP NQVQ NRVR NSVS OTUT PUTU aLaNcNcLaL bLbN aMcM aVaXcXcVaV bVbX aWcW", + "D`H@Hd M@Md W@Wd \\@\\d MMWK MNWL MOWM MWWU MXWV MYWW", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "NVQQQSSSSQQQ QQSS SQQS", + "JZMPQRTTVVWYW[V]U^ MQST MRPSTUVWWY", + "JZWKVMTOPQMR SPMS UFVGWIWKVNTPQRMT", + "H\\PMMNLOKQKSLUMVPWTWWVXUYSYQXOWNTMPM MNLPLSMUNVPW WVXTXQWOVNTM", + "H\\SMONLPKRKTLVNWQWUVXTYRYPXNVMSM XNSM VMQNLP ONKR LVQW NWSVXT UVYR", + "J[SMPNNPMRMTNVPWRWUVWTXRXPWNUMSM OPUM NRVN MTWO NUXP OVWR PWVT", + "JZOGO^ UFU] MNWL MOWM MWWU MXWV", + "JZNFNX VLV^ NNVL NOVM NWVU NXVV", + "JZNBNW NNQLTLVMWOWQVSSUQVNW NNQMTMVN UMVOVQUSSU", + "E_HIHL \\I\\L HI\\I HJ\\J HK\\K HL\\L", + "JZMNMQ WNWQ MNWN MOWO MPWP MQWQ", + "JZQCVMRTRU ULQS TITKPRRUUY W\\UYSXQXOYN[N]O_Ra W\\UZSYOYO]P_Ra SXPZN]", + "JZPOOMOKMKMMNNPOSOUNWL NKNN MLOL MMSO POUN WLSY", + "A^GfHfIeIdHcGcFdFfGhIiKiNhPfQdR`RUQ;Q4R/S-U,V,X-Y/Y3X6W8U;P?JCHEFHEJDNDREVGYJ[N\\R\\V[XZZW[T[PZMYKWITHPHMIKKJNJRKUMW GdGeHeHdGd U;Q?LCIFGIFKENERFVGXJ[ R\\U[WZYWZTZPYMXKVITH", + "IjNQOOQNSNUOVQVSUUSVQVOUNTMQMNNKPISHWH[I^K`NaRaW`[_]]`ZcVfQiMk WHZI]K_N`R`W_[^]\\`YcTgQi POTO OPUP NQVQ NRVR NSVS OTUT PUTU eLeNgNgLeL fLfN eMgM eVeXgXgVeV fVfX eWgW", + "D`H>Hf I>If M>Mf QBSBSDQDQAR?T>W>Y?[A\\D\\I[LYNWOUOSNRLQNOQNROSQVRXSVUUWUYV[X\\[\\`[cYeWfTfReQcQ`S`SbQb RBRD QCSC Y?ZA[D[IZLYN RLRNPQNRPSRVRX YVZX[[[`ZcYe R`Rb QaSa", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "AcHBHb IBIb [B[b \\B\\b DB`B DbMb Wb`b", + "BaGBQPFb FBPP EBPQ EB\\B^I[B Ga\\a Fb\\b^[[b", + "I[X+U1R8P=OANFMNMVN^OcPgRlUsXy U1S6QPBTJTLSNROMRRUSVTXTZPbOfOjPoRsVy T.R2Q5P:P>QCRF R^QaPfPjQoRrTv", + "I\\N+R1T5U:U>TBPJPLQNROWRRUQVPXPZTbUfUjToRsNy P.R2S5T:T>SCRF R^SaTfTjSoRrPv", + "I[V.S1Q4O8N=NCOIPMSXT\\UbUgTlSoQs S1Q5P8O=OBPHQLTWU[VaVgUlSpQsNv", + "I[N.Q1S4U8V=VCUITMQXP\\ObOgPlQoSs Q1S5T8U=UBTHSLPWO[NaNgOlQpSsVv", + "7Z:RARRo @RQo ?RRr Z\"VJRr", + "Ca].\\.[/[0\\1]1^0^.],[+Y+W,U.T0S3R:QJQjPsOv \\/\\0]0]/\\/ R:Rj U.T1S:SZRjQqPtOvMxKyIyGxFvFtGsHsItIuHvGv GtGuHuHtGt", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "H\\RFJ[ RIK[J[ RIY[Z[ RFZ[ MUWU LVXV", + "H\\LFL[ MGMZ LFTFWGXHYJYMXOWPTQ MGTGWHXJXMWOTP MPTPWQXRYTYWXYWZT[L[ MQTQWRXTXWWYTZMZ", + "H]ZKYIWGUFQFOGMILKKNKSLVMXOZQ[U[WZYXZV ZKYKXIWHUGQGOHMKLNLSMVOYQZUZWYXXYVZV", + "H]LFL[ MGMZ LFSFVGXIYKZNZSYVXXVZS[L[ MGSGVHWIXKYNYSXVWXVYSZMZ", + "I\\MFM[ NGNZ MFYF NGYGYF NPTPTQ NQTQ NZYZY[ M[Y[", + "I[MFM[ NGN[M[ MFYF NGYGYF NPTPTQ NQTQ", + "H]ZKYIWGUFQFOGMILKKNKSLVMXOZQ[U[WZYXZVZRUR ZKYKXIWHUGQGOHNIMKLNLSMVNXOYQZUZWYXXYVYSUSUR", + "G]KFK[ KFLFL[K[ YFXFX[Y[ YFY[ LPXP LQXQ", + "NWRFR[S[ RFSFS[", + "J[VFVVUYSZQZOYNVMV VFWFWVVYUZS[Q[OZNYMV", + "H]LFL[M[ LFMFM[ ZFYFMR ZFMS POY[Z[ QOZ[", + "IZMFM[ MFNFNZ NZYZY[ M[Y[", + "F^JFJ[ KKK[J[ KKR[ JFRX ZFRX YKR[ YKY[Z[ ZFZ[", + "G]KFK[ LIL[K[ LIY[ KFXX XFXX XFYFY[", + "G]PFNGLIKKJNJSKVLXNZP[T[VZXXYVZSZNYKXIVGTFPF QGNHLKKNKSLVNYQZSZVYXVYSYNXKVHSGQG", + "H\\LFL[ MGM[L[ LFUFWGXHYJYMXOWPUQMQ MGUGWHXJXMWOUPMP", + "G]PFNGLIKKJNJSKVLXNZP[T[VZXXYVZSZNYKXIVGTFPF QGNHLKKNKSLVNYQZSZVYXVYSYNXKVHSGQG SXX]Y] SXTXY]", + "H\\LFL[ MGM[L[ LFTFWGXHYJYMXOWPTQMQ MGTGWHXJXMWOTPMP RQX[Y[ SQY[", + "H\\YIWGTFPFMGKIKKLMMNOOTQVRWSXUXXWYTZPZNYMXKX YIWIVHTGPGMHLILKMMONTPVQXSYUYXWZT[P[MZKX", + "J[RGR[ SGS[R[ LFYFYG LFLGYG", + "G]KFKULXNZQ[S[VZXXYUYF KFLFLUMXNYQZSZVYWXXUXFYF", + "H\\JFR[ JFKFRX ZFYFRX ZFR[", + "E_GFM[ GFHFMX RFMX RIM[ RIW[ RFWX ]F\\FWX ]FW[", + "H\\KFX[Y[ KFLFY[ YFXFK[ YFL[K[", + "I\\KFRPR[S[ KFLFSP ZFYFRP ZFSPS[", + "H\\XFK[ YFL[ KFYF KFKGXG LZYZY[ K[Y[", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "E\\XFVHTKQPOSLWIZG[E[DZDXEWFXEY XFWJUTT[ XFU[ T[TYSVRTPRNQLQKRKTLWOZR[V[XZ", + "F^UGTHSJQOOUNWLZJ[ THSKQSPVOXMZJ[H[GZGXHWIXHY OLNNMOKOJNJLKJMHOGRFXFZG[I[KZMXNTORO XFYGZIZKYMXN TOWPXQYSYVXYWZU[S[RZRXSU TOVPWQXSXVWYU[", + "H]KHJJJLKNNOQOUNWMYKZIZGYFWFTGQJOMMQLULXMZP[R[UZWXXVXTWRURSSRU WFUGRJPMNQMUMXNZP[", + "F]UGTHSJQOOUNWLZJ[ THSKQSPVOXMZJ[H[GZGXHWJWLXNZP[S[UZWXYTZOZLYIWGUFPFMGKIJKJMKNMNNMOK", + "I\\WIVJVLWMYMZKZIYGWFTFRGQHPJPLQNSO TFRHQJQMSO SOQONPLRKTKWLYMZO[R[UZWXXVXTWRURSSRU QOOPMRLTLXMZ", + "G\\WHVJTORUQWOZM[ QLPNNOLOKMKKLINGQF[FXGWHVKTSSVRXPZM[K[IZHYHXIWJXIY SFWGXG OSPRRQVQXPZMXT", + "G]JIIKIMJOLPOPROTNWKXHXGWFVFTGRIQKPNPQQSSTUTWSYQZO WFUGSIRKQNQRST ZOYSWWUYSZO[L[JZIXIWJVKWJX YSWVUXRZO[", + "F^LLKKKILGOFRFOQMWLYKZI[G[FZFXGWHXGY RFOONRLWKYI[ JTKSMRVOXN[L]J^H^G]F\\FZGXJWLURTVTYV[W[YZ[X \\FZHXLVRUVUYV[", + "IYWHUKSPQUPWNZL[ YLWNTOQOONNLNJOHQGUFYFWHVJTPRVQXOZL[J[IZIXJWKXJY", + "IZYFWHUKSPPYN] YMWOTPQPOONMNKOIQGUFYFWIVKSTQXPZN]M^K_J^J\\KZMXOWRVVU", + "F^LLKKKIMGPFRFOQMWLYKZI[G[FZFXGWHXGY RFOONRLWKYI[ ZGWKUMSNPO ]G\\H]I^H^G]F\\FZGWLVMTNPO POSPTRUYV[ PORPSRTYV[W[YZ[X", + "I[MILKLMMOOPRPUOWNZK[H[GZFYFWGVHTKPUOWMZK[ VHTLRSQVPXNZK[I[HZHXIWKWMXPZR[U[WZYX", + "D`RFNOKUIXGZE[C[BZBXCWDXCY RFPMOQNVNZP[ RFQJPOOVOZP[ [FWORXP[ [FYMXQWVWZY[Z[\\Z^X [FZJYOXVXZY[", + "G^RFQJOPMULWJZH[F[EZEXFWGXFY RFRKSVT[ RFSKTVT[ `G_H`IaHaG`F^F\\GZJYLWQUWT[", + "H]SFQGOIMLLNKRKVLYMZO[Q[TZVXXUYSZOZKYHXGWGUHSJQNPSPV QGOJMNLRLVMYO[", + "F]UGTHSJQOOUNWLZJ[ THSKQSPVOXMZJ[H[GZGXHWIXHY OLNNMOKOJNJLKJMHOGRFVFYGZH[J[MZOYPVQTQRP VFXGYHZJZMYOXPVQ", + "H]UJULTNSOQPOPNNNLOIQGTFWFYGZIZMYPWSSWPYNZK[I[HZHXIWKWMXPZS[V[XZZX WFXGYIYMXPVSSVOYK[", + "F^UGTHSJQOOUNWLZJ[ THSKQSPVOXMZJ[H[GZGXHWIXHY OLNNMOKOJNJLKJMHOGRFWFZG[I[KZMYNVORO WFYGZIZKYMXNVO ROUPVRWYX[ ROTPURVYX[Y[[Z]X", + "H\\NIMKMMNOPPSPVOXN[K\\H\\G[FZFXGWHVJUMSTRWPZN[ VJUNTUSXQZN[K[IZHXHWIVJWIX", + "I[YHXJVOTUSWQZO[ SLRNPONOMMMKNIPGSF\\FZGYHXKVSUVTXRZO[M[KZJYJXKWLXKY UFYGZG", + "G]HJJGLFMFOHOKNNKVKYL[ MFNHNKKSJVJYL[N[PZSWUTVR ZFVRUVUYW[X[ZZ\\X [FWRVVVYW[", + "G\\HJJGLFMFOHOKNOLVLYM[ MFNHNKLRKVKYM[N[QZTWVTXPYMZIZGYFXFWGVIVLWNYP[Q]Q", + "F]ILHLGKGIHGJFNFMHLLKUJ[ LLLUK[ VFTHRLOUMYK[ VFUHTLSUR[ TLTUS[ `F^G\\IZLWUUYS[", + "H\\PKOLMLLKLIMGOFQFSGTITLSPQUOXMZJ[H[GZGXHWIXHY QFRGSISLRPPUNXLZJ[ ]G\\H]I^H^G]F[FYGWIULSPRURXSZT[U[WZYX", + "G]JJLGNFOFQGQIOOORPT OFPGPINONRPTRTUSWQYNZL \\FZLWTUX ]F[LYQWUUXSZP[L[JZIXIWJVKWJX", + "G\\ZHYJWOVRUTSWQYOZL[ SLRNPONOMMMKNIPGSF]F[GZHYKXOVUTXQZL[H[GZGXHWJWLXOZQ[T[WZYX VFZG[G", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "H\\WMW[X[ WMXMX[ WPUNSMPMNNLPKSKULXNZP[S[UZWX WPSNPNNOMPLSLUMXNYPZSZWX", + "H\\LFL[M[ LFMFM[ MPONQMTMVNXPYSYUXXVZT[Q[OZMX MPQNTNVOWPXSXUWXVYTZQZMX", + "I[XPVNTMQMONMPLSLUMXOZQ[T[VZXX XPWQVOTNQNOONPMSMUNXOYQZTZVYWWXX", + "H\\WFW[X[ WFXFX[ WPUNSMPMNNLPKSKULXNZP[S[UZWX WPSNPNNOMPLSLUMXNYPZSZWX", + "I[MTXTXQWOVNTMQMONMPLSLUMXOZQ[T[VZXX MSWSWQVOTNQNOONPMSMUNXOYQZTZVYWWXX", + "LZWFUFSGRJR[S[ WFWGUGSH TGSJS[ OMVMVN OMONVN", + "H\\XMWMW\\V_U`SaQaO`N_L_ XMX\\W_UaSbPbNaL_ WPUNSMPMNNLPKSKULXNZP[S[UZWX WPSNPNNOMPLSLUMXNYPZSZWX", + "H\\LFL[M[ LFMFM[ MQPNRMUMWNXQX[ MQPORNTNVOWQW[X[", + "NWRFQGQHRISITHTGSFRF RGRHSHSGRG RMR[S[ RMSMS[", + "NWRFQGQHRISITHTGSFRF RGRHSHSGRG RMRbSb RMSMSb", + "H[LFL[M[ LFMFM[ XMWMMW XMMX PTV[X[ QSX[", + "NWRFR[S[ RFSFS[", + "CbGMG[H[ GMHMH[ HQKNMMPMRNSQS[ HQKOMNONQORQR[S[ SQVNXM[M]N^Q^[ SQVOXNZN\\O]Q][^[", + "H\\LML[M[ LMMMM[ MQPNRMUMWNXQX[ MQPORNTNVOWQW[X[", + "I\\QMONMPLSLUMXOZQ[T[VZXXYUYSXPVNTMQM QNOONPMSMUNXOYQZTZVYWXXUXSWPVOTNQN", + "H\\LMLbMb LMMMMb MPONQMTMVNXPYSYUXXVZT[Q[OZMX MPQNTNVOWPXSXUWXVYTZQZMX", + "H\\WMWbXb WMXMXb WPUNSMPMNNLPKSKULXNZP[S[UZWX WPSNPNNOMPLSLUMXNYPZSZWX", + "KYOMO[P[ OMPMP[ PSQPSNUMXM PSQQSOUNXNXM", + "J[XPWNTMQMNNMPNRPSUUWV VUWWWXVZ WYTZQZNY OZNXMX XPWPVN WOTNQNNO ONNPOR NQPRUTWUXWXXWZT[Q[NZMX", + "MXRFR[S[ RFSFS[ OMVMVN OMONVN", + "H\\LMLWMZO[R[TZWW LMMMMWNYPZRZTYWW WMW[X[ WMXMX[", + "JZLMR[ LMMMRY XMWMRY XMR[", + "F^IMN[ IMJMNX RMNX RPN[ RPV[ RMVX [MZMVX [MV[", + "I[LMW[X[ LMMMX[ XMWML[ XMM[L[", + "JZLMR[ LMMMRY XMWMRYNb XMR[ObNb", + "I[VNL[ XMNZ LMXM LMLNVN NZXZX[ L[X[", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "K[UUTSRRPRNSMTLVLXMZO[Q[SZTX PRNTMVMYO[ VRTXTZV[XZYY[V WRUXUZV[", + "LZLVNSPO SFMXMZO[P[RZTXUUURVVWWXWZV TFNXNZO[", + "LXTSSTTTTSSRQROSNTMVMXNZP[S[VYXV QROTNVNYP[", + "K[UUTSRRPRNSMTLVLXMZO[Q[SZTX PRNTMVMYO[ ZFTXTZV[XZYY[V [FUXUZV[", + "LXOYQXRWSUSSRRQROSNTMVMXNZP[S[VYXV QROTNVNYP[", + "OXRRUOWLXIXGWFUGTIKdKfLgNfOcPZQ[S[UZVYXV TISNRRO[M`Kd", + "K[UUTSRRPRNSMTLVLXMZO[Q[SZTX PRNTMVMYO[ VRPd WRT[R`PdOfMgLfLdMaO_R]V[YY[V", + "L[LVNSPO SFL[ TFM[ OUQSSRTRVSVUUXUZV[ TRUSUUTXTZV[XZYY[V", + "NVSLRMSNTMSL QROXOZQ[SZTYVV RRPXPZQ[", + "NVSLRMSNTMSL QRKd RRO[M`KdJfHgGfGdHaJ_M]Q[TYVV", + "LZLVNSPO SFL[ TFM[ URUSVSURTRRTOU OURVSZT[ OUQVRZT[U[XYZV", + "NVNVPSRO UFOXOZQ[SZTYVV VFPXPZQ[", + "E^EVGSIRKSKUI[ IRJSJUH[ KUMSORPRRSRUP[ PRQSQUO[ RUTSVRWRYSYUXXXZY[ WRXSXUWXWZY[[Z\\Y^V", + "I[IVKSMROSOUM[ MRNSNUL[ OUQSSRTRVSVUUXUZV[ TRUSUUTXTZV[XZYY[V", + "KYRRPRNSMTLVLXMZO[Q[SZTYUWUUTSRRQSQURWTXVXXWYV PRNTMVMYO[", + "L[LVNSPO QLHg RLIg OUQSSRTRVSVUUXUZV[ TRUSUUTXTZV[XZYY[V", + "K[UUTSRRPRNSMTLVLXMZO[Q[SZ PRNTMVMYO[ VRPdPfQgSfTcT[V[YY[V WRT[R`Pd", + "LZLVNSPRRSRUP[ PRQSQUO[ RUTSVRWRVU VRVUWWXWZV", + "NZNVPSQQQSTUUWUYTZR[ QSSUTWTYR[ NZP[U[XYZV", + "NVNVPSRO UFOXOZQ[SZTYVV VFPXPZQ[ PNVN", + "K[NRLXLZN[O[QZSXUU ORMXMZN[ VRTXTZV[XZYY[V WRUXUZV[", + "KZNRMTLWLZN[O[RZTXUUUR ORNTMWMZN[ URVVWWXWZV", + "H]LRJTIWIZK[L[NZPX MRKTJWJZK[ RRPXPZR[S[UZWXXUXR SRQXQZR[ XRYVZW[W]V", + "JZJVLSNRPRQSQUPXOZM[L[KZKYLYKZ WSVTWTWSVRURSSRUQXQZR[U[XYZV QSRU SSQU PXQZ QXOZ", + "K[NRLXLZN[O[QZSXUU ORMXMZN[ VRPd WRT[R`PdOfMgLfLdMaO_R]V[YY[V", + "LYLVNSPRRRTSTVSXPZN[ RRSSSVRXPZ N[P\\Q^QaPdNfLgKfKdLaO^R\\VYYV N[O\\P^PaOdNf", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "NV", + "JZ", + "H\\QFNGLJKOKRLWNZQ[S[VZXWYRYOXJVGSFQF OGMJLOLRMWOZ NYQZSZVY UZWWXRXOWJUG VHSGQGNH", + "H\\NJPISFS[ NJNKPJRHR[S[", + "H\\LKLJMHNGPFTFVGWHXJXLWNUQL[ LKMKMJNHPGTGVHWJWLVNTQK[ LZYZY[ K[Y[", + "H\\MFXFQO MFMGWG WFPO QNSNVOXQYTYUXXVZS[P[MZLYKWLW POSOVPXS TOWQXTXUWXTZ XVVYSZPZMYLW OZLX", + "H\\UIU[V[ VFV[ VFKVZV UILV LUZUZV", + "H\\MFLO NGMN MFWFWG NGWG MNPMSMVNXPYSYUXXVZS[P[MZLYKWLW LOMOONSNVOXR TNWPXSXUWXTZ XVVYSZPZMYLW OZLX", + "H\\VGWIXIWGTFRFOGMJLOLTMXOZR[S[VZXXYUYTXQVOSNRNOOMQ WHTGRGOH PGNJMOMTNXQZ MVOYRZSZVYXV TZWXXUXTWQTO XSVPSOROOPMS QONQMT", + "H\\KFYFO[ KFKGXG XFN[O[", + "H\\PFMGLILKMMNNPOTPVQWRXTXWWYTZPZMYLWLTMRNQPPTOVNWMXKXIWGTFPF NGMIMKNMPNTOVPXRYTYWXYWZT[P[MZLYKWKTLRNPPOTNVMWKWIVG WHTGPGMH LXOZ UZXX", + "H\\WPURRSQSNRLPKMKLLINGQFRFUGWIXMXRWWUZR[P[MZLXMXNZ WMVPSR WNUQRRQRNQLN PRMPLMLLMIPG LKNHQGRGUHWK SGVIWMWRVWTZ UYRZPZMY", + "MXRXQYQZR[S[TZTYSXRX RYRZSZSYRY", + "MXTZS[R[QZQYRXSXTYT\\S^Q_ RYRZSZSYRY S[T\\ TZS^", + "MXRMQNQORPSPTOTNSMRM RNROSOSNRN RXQYQZR[S[TZTYSXRX RYRZSZSYRY", + "MXRMQNQORPSPTOTNSMRM RNROSOSNRN TZS[R[QZQYRXSXTYT\\S^Q_ RYRZSZSYRY S[T\\ TZS^", + "MXRFRTST RFSFST RXQYQZR[S[TZTYSXRX RYRZSZSYRY", + "I\\LKLJMHNGQFTFWGXHYJYLXNWOUPRQ LKMKMJNHQGTGWHXJXLWNUORP MIPG UGXI XMTP RPRTSTSP RXQYQZR[S[TZTYSXRX RYRZSZSYRY", + "MXTFRGQIQLRMSMTLTKSJRJQK RKRLSLSKRK RGQK QIRJ", + "MXTHSIRIQHQGRFSFTGTJSLQM RGRHSHSGRG SITJ THSL", + "F_\\MZMXNWPUVTXSYQZMZKYJWJUKSLRQOSMTKTISGQFPFNGMIMKNNPQUWXZZ[\\[ \\M\\NZNWP ZMXPVVUXSZQ[M[KZJYIWIUJSLQQNRMSKSIRG SHQGPGNH OGNINKONQQVWXYZZ\\Z\\[", + "I\\RBR_S_ RBSBS_ WIYIWGTFQFNGLILKMMNNVRWSXUXWWYTZQZOYNX WIVHTGQGNHMIMKNMVQXSYUYWXYWZT[Q[NZLXNX XXUZ", + "G^[BIbJb [B\\BJb", + "KYUBSDQGOKNPNTOYQ]S`UbVb UBVBTDRGPKOPOTPYR]T`Vb", + "KYNBPDRGTKUPUTTYR]P`NbOb NBOBQDSGUKVPVTUYS]Q`Ob", + "JZRFQGSQRR RFRR RFSGQQRR MINIVOWO MIWO MIMJWNWO WIVINOMO WIMO WIWJMNMO", + "F_JQ[Q[R JQJR[R", + "F_RIRZSZ RISISZ JQ[Q[R JQJR[R", + "F_JM[M[N JMJN[N JU[U[V JUJV[V", + "NWSFRGRM SGRM SFTGRM", + "I[NFMGMM NGMM NFOGMM WFVGVM WGVM WFXGVM", + "KYQFOGNINKOMQNSNUMVKVIUGSFQF QFNIOMSNVKUGQF SFOGNKQNUMVISF", + "F^ZIJRZ[ ZIZJLRZZZ[", + "F^JIZRJ[ JIJJXRJZJ[", + "G^OFObPb OFPFPb UFUbVb UFVFVb JP[P[Q JPJQ[Q JW[W[X JWJX[X", + "F^[FYGVHSHPGNFLFJGIIIKKMMMOLPJPHNF [FH[I[ [F\\FI[ YTWTUUTWTYV[X[ZZ[X[VYT NFJGIKMMPJNF LFIIKMOLPHLF YTUUTYX[[XYT WTTWV[ZZ[VWT", + "E`WMTKQKOLNMMOMRNTOUQVTVWT WMTLQLOMNONROTQUTUWT VKVSWUYVZV\\U]S]O\\L[JYHWGTFQFNGLHJJILHOHRIUJWLYNZQ[U[YZ VKWKWSXUZV YV[U\\S\\O[LZJYIWHTGQGNHLIKJJLIOIRJUKWLXNYQZUZYYYZ", + "E_JPLONOPPSTTUVVXVZU[S[QZOXNVNTOSPPTNULUJT ZPXOVOTPQTPUNVLVJUISIQJOLNNNPOQPTTVUXUZT KOJQJSKU YUZSZQYO", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "NV", + "JZ", + "H]TFQGOIMLLOKSKVLYMZO[Q[TZVXXUYRZNZKYHXGVFTF TFRGPINLMOLSLVMYO[ Q[SZUXWUXRYNYKXHVF", + "H]TJO[ VFP[ VFSIPKNL UIQKNL", + "H]OJPKOLNKNJOHPGSFVFYGZIZKYMWOTQPSMUKWI[ VFXGYIYKXMVOPS JYKXMXRZUZWYXW MXR[U[WZXW", + "H]OJPKOLNKNJOHPGSFVFYGZIZKYMVOSP VFXGYIYKXMVO QPSPVQWRXTXWWYVZS[O[LZKYJWJVKULVKW SPUQVRWTWWVYUZS[", + "H]XGR[ YFS[ YFJUZU", + "H]QFLP QF[F QGVG[F LPMOPNSNVOWPXRXUWXUZR[O[LZKYJWJVKULVKW SNUOVPWRWUVXTZR[", + "H]YIXJYKZJZIYGWFTFQGOIMLLOKSKWLYMZO[R[UZWXXVXSWQVPTOQOOPMRLT TFRGPINLMOLSLXMZ R[TZVXWVWRVP", + "H]NFLL [FZIXLSRQUPWO[ XLRRPUOWN[ MIPFRFWI NHPGRGWIYIZH[F", + "H]SFPGOHNJNMOOQPTPXOYNZLZIYGVFSF SFQGPHOJOMPOQP TPWOXNYLYIXGVF QPMQKSJUJXKZN[R[VZWYXWXTWRVQTP QPNQLSKUKXLZN[ R[UZVYWWWSVQ", + "H]YMXOVQTRQROQNPMNMKNIPGSFVFXGYHZJZNYRXUVXTZQ[N[LZKXKWLVMWLX OQNONKOIQGSF XGYIYNXRWUUXSZQ[", + "MXPYOZP[QZPY", + "MXP[OZPYQZQ[P]N_", + "MXSMRNSOTNSM PYOZP[QZ", + "MXSMRNSOTNSM P[OZPYQZQ[P]N_", + "MXUFTGRS UGRS UFVGRS PYOZP[QZPY", + "H]OJPKOLNKNJOHPGSFWFZG[I[KZMYNSPQQQSRTTT WFYGZIZKYMXNVO PYOZP[QZPY", + "MXVFTHSJSKTLUKTJ", + "MXUHTGUFVGVHUJSL", + "E_\\N[O\\P]O]N\\M[MYNWPRXPZN[K[HZGXGVHTISKRPPROTMUKUITGRFPGOIOLPRQUSXUZW[Y[ZYZX K[IZHXHVITJSPP OLPQQTSWUYWZYZZY", + "H]TBL_ YBQ_ ZJYKZL[K[JZHYGVFRFOGMIMKNMONVRXT MKOMVQWRXTXWWYVZS[O[LZKYJWJVKULVKW", + "G]_BEb", + "KZZBVESHQKOONTNXO]P`Qb VESIQMPPOUOZP_Qb", + "JYSBTDUGVLVPUUSYQ\\N_Jb SBTEUJUOTTSWQ[N_", + "J[TFTR OIYO YIOO", + "E_IR[R", + "E_RIR[ IR[R", + "E_IO[O IU[U", + "NWUFSM VFSM", + "I[PFNM QFNM YFWM ZFWM", + "KZSFQGPIPKQMSNUNWMXKXIWGUFSF", + "F^ZIJRZ[", + "F^JIZRJ[", + "H]SFLb YFRb LQZQ KWYW", + "E_^F\\GXHUHQGOFMFKGJIJKLMNMPLQJQHOF ^FF[ XTVTTUSWSYU[W[YZZXZVXT", + "E`WNVLTKQKOLNMMPMSNUPVSVUUVS QKOMNPNSOUPV WKVSVUXVZV\\T]Q]O\\L[JYHWGTFQFNGLHJJILHOHRIUJWLYNZQ[T[WZYYZX XKWSWUXV", + "F_\\S[UYVWVUUTTQPPONNLNJOIQISJULVNVPUQTTPUOWNYN[O\\Q\\S", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "H\\RFK[ RFY[ RIX[ MUVU I[O[ U[[[", + "G]LFL[ MFM[ IFYFYLXF MPUPXQYRZTZWYYXZU[I[ UPWQXRYTYWXYWZU[", + "G]LFL[ MFM[ IFUFXGYHZJZLYNXOUP UFWGXHYJYLXNWOUP MPUPXQYRZTZWYYXZU[I[ UPWQXRYTYWXYWZU[", + "I[NFN[ OFO[ KFZFZLYF K[R[", + "F^NFNLMTLXKZJ[ XFX[ YFY[ KF\\F G[\\[ G[Gb H[Gb [[\\b \\[\\b", + "G\\LFL[ MFM[ SLST IFYFYLXF MPSP I[Y[YUX[", + "CbRFR[ SFS[ OFVF GGHHGIFHFGGFHFIGJIKMLONPWPYOZM[I\\G]F^F_G_H^I]H^G NPLQKSJXIZH[ NPMQLSKXJZI[G[FZEX WPYQZS[X\\Z][ WPXQYSZX[Z\\[^[_Z`X O[V[", + "H\\LIKFKLLINGPFTFWGXIXLWNTOQO TFVGWIWLVNTO TOVPXRYTYWXYWZT[O[MZLYKWKVLUMVLW WQXTXWWYVZT[", + "F^KFK[ LFL[ XFX[ YFY[ HFOF UF\\F XHLY H[O[ U[\\[", + "F^KFK[ LFL[ XFX[ YFY[ HFOF UF\\F XHLY H[O[ U[\\[ N@N?M?M@NBPCTCVBW@", + "F^KFK[ LFL[ HFOF LPSPUOVMWIXGYFZF[G[HZIYHZG SPUQVSWXXZY[ SPTQUSVXWZX[Z[[Z\\X H[O[", + "E^MFMLLTKXJZI[H[GZGYHXIYHZ XFX[ YFY[ JF\\F U[\\[", + "F_KFK[ LFRX KFR[ YFR[ YFY[ ZFZ[ HFLF YF]F H[N[ V[][", + "F^KFK[ LFL[ XFX[ YFY[ HFOF UF\\F LPXP H[O[ U[\\[", + "G]QFNGLIKKJOJRKVLXNZQ[S[VZXXYVZRZOYKXIVGSFQF QFOGMILKKOKRLVMXOZQ[ S[UZWXXVYRYOXKWIUGSF", + "F^KFK[ LFL[ XFX[ YFY[ HF\\F H[O[ U[\\[", + "G]LFL[ MFM[ IFUFXGYHZJZMYOXPUQMQ UFWGXHYJYMXOWPUQ I[P[", + "G\\XIYLYFXIVGSFQFNGLIKKJNJSKVLXNZQ[S[VZXXYV QFOGMILKKNKSLVMXOZQ[", + "I\\RFR[ SFS[ LFKLKFZFZLYF O[V[", + "H]KFRV LFSV ZFSVQYPZN[M[LZLYMXNYMZ IFOF VF\\F", + "F_RFR[ SFS[ OFVF PILJJLIOIRJULWPXUXYW[U\\R\\O[LYJUIPI PIMJKLJOJRKUMWPX UXXWZU[R[OZLXJUI O[V[", + "H\\KFX[ LFY[ YFK[ IFOF UF[F I[O[ U[[[", + "F^KFK[ LFL[ XFX[ YFY[ HFOF UF\\F H[\\[ [[\\b \\[\\b", + "F]KFKQLSOTRTUSWQ LFLQMSOT WFW[ XFX[ HFOF TF[F T[[[", + "BcGFG[ HFH[ RFR[ SFS[ ]F][ ^F^[ DFKF OFVF ZFaF D[a[", + "BcGFG[ HFH[ RFR[ SFS[ ]F][ ^F^[ DFKF OFVF ZFaF D[a[ `[ab a[ab", + "F`PFP[ QFQ[ IFHLHFTF QPXP[Q\\R]T]W\\Y[ZX[M[ XPZQ[R\\T\\W[YZZX[", + "CaHFH[ IFI[ EFLF IPPPSQTRUTUWTYSZP[E[ PPRQSRTTTWSYRZP[ [F[[ \\F\\[ XF_F X[_[", + "H]MFM[ NFN[ JFQF NPUPXQYRZTZWYYXZU[J[ UPWQXRYTYWXYWZU[", + "H]LIKFKLLINGQFSFVGXIYKZNZSYVXXVZS[P[MZLYKWKVLUMVLW SFUGWIXKYNYSXVWXUZS[ PPYP", + "CbHFH[ IFI[ EFLF E[L[ VFSGQIPKOOORPVQXSZV[X[[Z]X^V_R_O^K]I[GXFVF VFTGRIQKPOPRQVRXTZV[ X[ZZ\\X]V^R^O]K\\IZGXF IPOP", + "G]WFW[ XFX[ [FOFLGKHJJJLKNLOOPWP OFMGLHKJKLLNMOOP RPPQORLYKZJZIY PQOSMZL[J[IYIX T[[[", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "I]NONPMPMONNPMTMVNWOXQXXYZZ[ WOWXXZZ[[[ WQVRPSMTLVLXMZP[S[UZWX PSNTMVMXNZP[", + "H\\XFWGQINKLNKQKULXNZQ[S[VZXXYUYSXPVNSMQMNNLPKS XFWHUIQJNLLN QMONMPLSLUMXOZQ[ S[UZWXXUXSWPUNSM", + "H\\MMM[ NMN[ JMUMXNYPYQXSUT UMWNXPXQWSUT NTUTXUYWYXXZU[J[ UTWUXWXXWZU[", + "HZMMM[ NMN[ JMXMXRWM J[Q[", + "F]NMNQMWLZK[ WMW[ XMX[ KM[M I[H`H[[[[`Z[", + "H[LSXSXQWOVNTMQMNNLPKSKULXNZQ[S[VZXX WSWPVN QMONMPLSLUMXOZQ[", + "E`RMR[ SMS[ OMVM JNIOHNIMJMKNMRNSPTUTWSXRZN[M\\M]N\\O[N PTNUMVKZJ[ PTNVLZK[I[HZGX UTWUXVZZ[[ UTWVYZZ[\\[]Z^X O[V[", + "I[MOLMLQMONNPMTMWNXPXQWSTT TMVNWPWQVSTT QTTTWUXWXXWZT[P[MZLXLWMVNWMX TTVUWWWXVZT[", + "G]LML[ MMM[ WMW[ XMX[ IMPM TM[M I[P[ T[[[ WNMZ", + "G]LML[ MMM[ WMW[ XMX[ IMPM TM[M I[P[ T[[[ WNMZ OGOFNFNGOIQJSJUIVG", + "H\\MMM[ NMN[ JMQM NTPTSSTRVNWMXMYNXOWN PTSUTVVZW[ PTRUSVUZV[X[YZZX J[Q[", + "G]NMNQMWLZK[J[IZJYKZ WMW[ XMX[ KM[M T[[[", + "G^LML[ LMR[ MMRY XMR[ XMX[ YMY[ IMMM XM\\M I[O[ U[\\[", + "G]LML[ MMM[ WMW[ XMX[ IMPM TM[M MTWT I[P[ T[[[", + "H\\QMNNLPKSKULXNZQ[S[VZXXYUYSXPVNSMQM QMONMPLSLUMXOZQ[ S[UZWXXUXSWPUNSM", + "G]LML[ MMM[ WMW[ XMX[ IM[M I[P[ T[[[", + "G\\LMLb MMMb MPONQMSMVNXPYSYUXXVZS[Q[OZMX SMUNWPXSXUWXUZS[ IMMM IbPb", + "H[WPVQWRXQXPVNTMQMNNLPKSKULXNZQ[S[VZXX QMONMPLSLUMXOZQ[", + "I\\RMR[ SMS[ MMLRLMYMYRXM O[V[", + "I[LMR[ MMRY XMR[P_NaLbKbJaK`La JMPM TMZM", + "H]RFRb SFSb OFSF RPQNPMNMLNKQKWLZN[P[QZRX NMMNLQLWMZN[ WMXNYQYWXZW[ SPTNUMWMYNZQZWYZW[U[TZSX ObVb", + "H\\LMW[ MMX[ XML[ JMPM TMZM J[P[ T[Z[", + "G]LML[ MMM[ WMW[ XMX[ IMPM TM[M I[[[[`Z[", + "G]LMLTMVPWRWUVWT MMMTNVPW WMW[ XMX[ IMPM TM[M T[[[", + "CbHMH[ IMI[ RMR[ SMS[ \\M\\[ ]M][ EMLM OMVM YM`M E[`[", + "CbHMH[ IMI[ RMR[ SMS[ \\M\\[ ]M][ EMLM OMVM YM`M E[`[``_[", + "H]QMQ[ RMR[ LMKRKMUM RTVTYUZWZXYZV[N[ VTXUYWYXXZV[", + "E_JMJ[ KMK[ GMNM KTOTRUSWSXRZO[G[ OTQURWRXQZO[ YMY[ ZMZ[ VM]M V[][", + "J[OMO[ PMP[ LMSM PTTTWUXWXXWZT[L[ TTVUWWWXVZT[", + "I\\MOLMLQMONNPMSMVNXPYSYUXXVZS[P[NZLXLWMVNWMX SMUNWPXSXUWXUZS[ RTXT", + "DaIMI[ JMJ[ FMMM F[M[ VMSNQPPSPUQXSZV[X[[Z]X^U^S]P[NXMVM VMTNRPQSQURXTZV[ X[ZZ\\X]U]S\\PZNXM JTPT", + "G\\VMV[ WMW[ ZMOMLNKPKQLSOTVT OMMNLPLQMSOT TTQUPVNZM[ TTRUQVOZN[L[KZJX S[Z[", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "H\\RFKZ QIW[ RIX[ RFY[ MUVU I[O[ T[[[ KZJ[ KZM[ WZU[ WYV[ XYZ[", + "G]LFL[ MGMZ NFN[ IFUFXGYHZJZLYNXOUP XHYJYLXN UFWGXIXMWOUP NPUPXQYRZTZWYYXZU[I[ XRYTYWXY UPWQXSXXWZU[ JFLG KFLH OFNH PFNG LZJ[ LYK[ NYO[ NZP[", + "G\\XIYFYLXIVGTFQFNGLIKKJNJSKVLXNZQ[T[VZXXYV MILKKNKSLVMX QFOGMJLNLSMWOZQ[", + "G]LFL[ MGMZ NFN[ IFSFVGXIYKZNZSYVXXVZS[I[ WIXKYNYSXVWX SFUGWJXNXSWWUZS[ JFLG KFLH OFNH PFNG LZJ[ LYK[ NYO[ NZP[", + "G\\LFL[ MGMZ NFN[ IFYFYL NPTP TLTT I[Y[YU JFLG KFLH OFNH PFNG TFYG VFYH WFYI XFYL TLSPTT TNRPTR TOPPTQ LZJ[ LYK[ NYO[ NZP[ T[YZ V[YY W[YX X[YU", + "G[LFL[ MGMZ NFN[ IFYFYL NPTP TLTT I[Q[ JFLG KFLH OFNH PFNG TFYG VFYH WFYI XFYL TLSPTT TNRPTR TOPPTQ LZJ[ LYK[ NYO[ NZP[", + "G^XIYFYLXIVGTFQFNGLIKKJNJSKVLXNZQ[T[VZXZY[YS MILKKNKSLVMX QFOGMJLNLSMWOZQ[ XTXY WSWYVZ TS\\S USWT VSWU ZSYU [SYT", + "F^KFK[ LGLZ MFM[ WFW[ XGXZ YFY[ HFPF TF\\F MPWP H[P[ T[\\[ IFKG JFKH NFMH OFMG UFWG VFWH ZFYH [FYG KZI[ KYJ[ MYN[ MZO[ WZU[ WYV[ YYZ[ YZ[[", + "LXQFQ[ RGRZ SFS[ NFVF N[V[ OFQG PFQH TFSH UFSG QZO[ QYP[ SYT[ SZU[", + "JZSFSWRZQ[ TGTWSZ UFUWTZQ[O[MZLXLVMUNUOVOWNXMX MVMWNWNVMV PFXF QFSG RFSH VFUH WFUG", + "F\\KFK[ LGLZ MFM[ XGMR PPW[ QPX[ QNY[ HFPF UF[F H[P[ T[[[ IFKG JFKH NFMH OFMG WFXG ZFXG KZI[ KYJ[ MYN[ MZO[ WYU[ WYZ[", + "I[NFN[ OGOZ PFP[ KFSF K[Z[ZU LFNG MFNH QFPH RFPG NZL[ NYM[ PYQ[ PZR[ U[ZZ W[ZY X[ZX Y[ZU", + "E_JFJZ JFQ[ KFQX LFRX XFQ[ XFX[ YGYZ ZFZ[ GFLF XF]F G[M[ U[][ HFJG [FZH \\FZG JZH[ JZL[ XZV[ XYW[ ZY[[ ZZ\\[", + "F^KFKZ KFY[ LFXX MFYX YGY[ HFMF VF\\F H[N[ IFKG WFYG [FYG KZI[ KZM[", + "G]QFNGLIKKJOJRKVLXNZQ[S[VZXXYVZRZOYKXIVGSFQF MILKKNKSLVMX WXXVYSYNXKWI QFOGMJLNLSMWOZQ[ S[UZWWXSXNWJUGSF", + "G]LFL[ MGMZ NFN[ IFUFXGYHZJZMYOXPUQNQ XHYJYMXO UFWGXIXNWPUQ I[Q[ JFLG KFLH OFNH PFNG LZJ[ LYK[ NYO[ NZP[", + "G]QFNGLIKKJOJRKVLXNZQ[S[VZXXYVZRZOYKXIVGSFQF MILKKNKSLVMX WXXVYSYNXKWI QFOGMJLNLSMWOZQ[ S[UZWWXSXNWJUGSF NXOVQURUTVUXV^W`Y`Z^Z\\ V\\W^X_Y_ UXW]X^Y^Z]", + "G]LFL[ MGMZ NFN[ IFUFXGYHZJZLYNXOUPNP XHYJYLXN UFWGXIXMWOUP RPTQUSWYX[Z[[Y[W WWXYYZZZ TQURXXYYZY[X I[Q[ JFLG KFLH OFNH PFNG LZJ[ LYK[ NYO[ NZP[", + "H\\XIYFYLXIVGSFPFMGKIKLLNOPURWSXUXXWZ LLMNOOUQWRXT MGLILKMMONUPXRYTYWXYWZT[Q[NZLXKUK[LX", + "H\\JFJL QFQ[ RGRZ SFS[ ZFZL JFZF N[V[ KFJL LFJI MFJH OFJG UFZG WFZH XFZI YFZL QZO[ QYP[ SYT[ SZU[", + "F^KFKULXNZQ[S[VZXXYUYG LGLVMX MFMVNYOZQ[ HFPF VF\\F IFKG JFKH NFMH OFMG WFYG [FYG", + "H\\KFR[ LFRXR[ MFSX YGR[ IFPF UF[F JFLH NFMH OFMG WFYG ZFYG", + "F^JFN[ KFNVN[ LFOV RFOVN[ RFV[ SFVVV[ TFWV ZGWVV[ GFOF RFTF WF]F HFKG IFKH MFLH NFLG XFZG \\FZG", + "H\\KFW[ LFX[ MFY[ XGLZ IFPF UF[F I[O[ T[[[ JFMH NFMH OFMG VFXG ZFXG LZJ[ LZN[ WZU[ WYV[ WYZ[", + "G]JFQQQ[ KFRQRZ LFSQS[ YGSQ HFOF VF\\F N[V[ IFKG NFLG WFYG [FYG QZO[ QYP[ SYT[ SZU[", + "H\\YFKFKL WFK[ XFL[ YFM[ K[Y[YU LFKL MFKI NFKH PFKG T[YZ V[YY W[YX X[YU", + "H\\RFKZ QIW[ RIX[ RFY[ MUVU I[O[ T[[[ KZJ[ KZM[ WZU[ WYV[ XYZ[", + "G]LFL[ MGMZ NFN[ IFUFXGYHZJZLYNXOUP XHYJYLXN UFWGXIXMWOUP NPUPXQYRZTZWYYXZU[I[ XRYTYWXY UPWQXSXXWZU[ JFLG KFLH OFNH PFNG LZJ[ LYK[ NYO[ NZP[", + "I[NFN[ OGOZ PFP[ KFZFZL K[S[ LFNG MFNH QFPH RFPG UFZG WFZH XFZI YFZL NYM[ NZL[ PYQ[ PZR[", + "H\\RFJ[ QIX[ RIY[ RFZ[ KYXY KZXZ J[Z[", + "G\\LFL[ MGMZ NFN[ IFYFYL NPTP TLTT I[Y[YU JFLG KFLH OFNH PFNG TFYG VFYH WFYI XFYL TLSPTT TNRPTR TOPPTQ LZJ[ LYK[ NYO[ NZP[ T[YZ V[YY W[YX X[YU", + "H\\YFKFKL WFK[ XFL[ YFM[ K[Y[YU LFKL MFKI NFKH PFKG T[YZ V[YY W[YX X[YU", + "F^KFK[ LGLZ MFM[ WFW[ XGXZ YFY[ HFPF TF\\F MPWP H[P[ T[\\[ IFKG JFKH NFMH OFMG UFWG VFWH ZFYH [FYG KZI[ KYJ[ MYN[ MZO[ WZU[ WYV[ YYZ[ YZ[[", + "G]QFNGLIKKJOJRKVLXNZQ[S[VZXXYVZRZOYKXIVGSFQF MILKKNKSLVMX WXXVYSYNXKWI QFOGMJLNLSMWOZQ[ S[UZWWXSXNWJUGSF OMOT UMUT OPUP OQUQ ONPP OOQP UNTP UOSP PQOS QQOR SQUR TQUS", + "LXQFQ[ RGRZ SFS[ NFVF N[V[ OFQG PFQH TFSH UFSG QZO[ QYP[ SYT[ SZU[", + "F\\KFK[ LGLZ MFM[ XGMR PPW[ QPX[ QNY[ HFPF UF[F H[P[ T[[[ IFKG JFKH NFMH OFMG WFXG ZFXG KZI[ KYJ[ MYN[ MZO[ WYU[ WYZ[", + "H\\RFKZ QIW[ RIX[ RFY[ I[O[ T[[[ KZJ[ KZM[ WZU[ WYV[ XYZ[", + "E_JFJZ JFQ[ KFQX LFRX XFQ[ XFX[ YGYZ ZFZ[ GFLF XF]F G[M[ U[][ HFJG [FZH \\FZG JZH[ JZL[ XZV[ XYW[ ZY[[ ZZ\\[", + "F^KFKZ KFY[ LFXX MFYX YGY[ HFMF VF\\F H[N[ IFKG WFYG [FYG KZI[ KZM[", + "G]JEJL ZEZL OMOT UMUT JUJ\\ ZUZ\\ JGZG JHZH JIZI OPUP OQUQ JXZX JYZY JZZZ JFMH ZFWH KIJK LIJJ XIZJ YIZK ONPP OOQP UNTP UOSP PQOS QQOR SQUR TQUS JVKX JWLX ZWXX ZVYX MYJ[ WYZ[", + "G]QFNGLIKKJOJRKVLXNZQ[S[VZXXYVZRZOYKXIVGSFQF MILKKNKSLVMX WXXVYSYNXKWI QFOGMJLNLSMWOZQ[ S[UZWWXSXNWJUGSF", + "F^KFK[ LGLZ MFM[ WFW[ XGXZ YFY[ HF\\F H[P[ T[\\[ IFKG JFKH NFMH OFMG UFWG VFWH ZFYH [FYG KZI[ KYJ[ MYN[ MZO[ WZU[ WYV[ YYZ[ YZ[[", + "G]LFL[ MGMZ NFN[ IFUFXGYHZJZMYOXPUQNQ XHYJYMXO UFWGXIXNWPUQ I[Q[ JFLG KFLH OFNH PFNG LZJ[ LYK[ NYO[ NZP[", + "G]IFPPQQ JFQP KFRPI[ IFYFZLYIWF VFYH TFYG KYYY JZYZ I[Y[ZUYXWY", + "H\\JFJL QFQ[ RGRZ SFS[ ZFZL JFZF N[V[ KFJL LFJI MFJH OFJG UFZG WFZH XFZI YFZL QZO[ QYP[ SYT[ SZU[", + "H\\JMKILGMFOFPGQIRM LHMGOGPH JMKJMHOHPIQMQ[ RMR[ ZMYJWHUHTISMS[ XHWGUGTH ZMYIXGWFUFTGSIRM N[V[ QYP[ QZO[ SZU[ SYT[", + "G]QFQ[ RGRZ SFS[ NFVF N[V[ OFQG PFQH TFSH UFSG QZO[ QYP[ SYT[ SZU[ OKLLKMJOJRKTLUOVUVXUYTZRZOYMXLUKOK LMKOKRLT XTYRYOXM OKMLLOLRMUOV UVWUXRXOWLUK", + "H\\KFW[ LFX[ MFY[ XGLZ IFPF UF[F I[O[ T[[[ JFMH NFMH OFMG VFXG ZFXG LZJ[ LZN[ WZU[ WYV[ WYZ[", + "F^QFQ[ RGRZ SFS[ NFVF N[V[ OFQG PFQH TFSH UFSG QZO[ QYP[ SYT[ SZU[ HMIMJNKQLSMTPUTUWTXSYQZN[M\\M LRKNJLILKN HMIKJKKLLPMSNTPU YN[LZLYNXR TUVTWSXPYLZK[K\\M", + "G]NYKYJWK[O[MVKRJOJLKIMGPFTFWGYIZLZOYRWVU[Y[ZWYYVY LSKOKLLI XIYLYOXS O[MULPLKMHNGPF TFVGWHXKXPWUU[ KZNZ VZYZ", + "H\\UFIZ SJT[ THUZ UFUHVYV[ LUTU F[L[ Q[X[ IZG[ IZK[ TZR[ TYS[ VYW[", + "F^OFI[ PFJ[ QFK[ LFWFZG[I[KZNYOVP YGZIZKYNXO WFXGYIYKXNVP NPVPXQYSYUXXVZR[F[ WQXSXUWXUZ VPWRWUVXTZR[ MFPG NFOH RFPH SFPG JZG[ JYH[ KYL[ JZM[", + "H]ZH[H\\F[L[JZHYGWFTFQGOIMLLOKSKVLYMZP[S[UZWXXV QHOJNLMOLSLWMY TFRGPJOLNOMSMXNZP[", + "F]OFI[ PFJ[ QFK[ LFUFXGYHZKZOYSWWUYSZO[F[ WGXHYKYOXSVWTY UFWHXKXOWSUWRZO[ MFPG NFOH RFPH SFPG JZG[ JYH[ KYL[ JZM[", + "F]OFI[ PFJ[ QFK[ ULST LF[FZL NPTP F[U[WV MFPG NFOH RFPH SFPG WFZG XFZH YFZI ZFZL ULSPST TNRPSR TOQPSQ JZG[ JYH[ KYL[ JZM[ P[UZ R[UY UYWV", + "F\\OFI[ PFJ[ QFK[ ULST LF[FZL NPTP F[N[ MFPG NFOH RFPH SFPG WFZG XFZH YFZI ZFZL ULSPST TNRPSR TOQPSQ JZG[ JYH[ KYL[ JZM[", + "H^ZH[H\\F[L[JZHYGWFTFQGOIMLLOKSKVLYMZP[R[UZWXYT QHOJNLMOLSLWMY VXWWXT TFRGPJOLNOMSMXNZP[ R[TZVWWT TT\\T UTWU VTWW ZTXV [TXU", + "E_NFH[ OFI[ PFJ[ ZFT[ [FU[ \\FV[ KFSF WF_F LPXP E[M[ Q[Y[ LFOG MFNH QFOH RFOG XF[G YFZH ]F[H ^F[G IZF[ IYG[ JYK[ IZL[ UZR[ UYS[ VYW[ UZX[", + "KYTFN[ UFO[ VFP[ QFYF K[S[ RFUG SFTH WFUH XFUG OZL[ OYM[ PYQ[ OZR[", + "I\\WFRWQYO[ XFTSSVRX YFUSSXQZO[M[KZJXJVKULUMVMWLXKX KVKWLWLVKV TF\\F UFXG VFWH ZFXH [FXG", + "F]OFI[ PFJ[ QFK[ \\GMR QOU[ ROV[ SNWZ LFTF YF_F F[N[ R[Y[ MFPG NFOH RFPH SFPG ZF\\G ^F\\G JZG[ JYH[ KYL[ JZM[ UZS[ UYT[ VYX[", + "H\\QFK[ RFL[ SFM[ NFVF H[W[YU OFRG PFQH TFRH UFRG LZI[ LYJ[ MYN[ LZO[ R[WZ T[XX V[YU", + "D`MFGZ MGNYN[ NFOY OFPX [FPXN[ [FU[ \\FV[ ]FW[ JFOF [F`F D[J[ R[Z[ KFMG LFMH ^F\\H _F\\G GZE[ GZI[ VZS[ VYT[ WYX[ VZY[", + "F_OFIZ OFV[ PFVX QFWX \\GWXV[ LFQF YF_F F[L[ MFPG NFPH ZF\\G ^F\\G IZG[ IZK[", + "G]SFPGNILLKOJSJVKYLZN[Q[TZVXXUYRZNZKYHXGVFSF OIMLLOKSKWLY UXWUXRYNYJXH SFQGOJNLMOLSLXMZN[ Q[SZUWVUWRXNXIWGVF", + "F]OFI[ PFJ[ QFK[ LFXF[G\\I\\K[NYPUQMQ ZG[I[KZNXP XFYGZIZKYNWPUQ F[N[ MFPG NFOH RFPH SFPG JZG[ JYH[ KYL[ JZM[", + "G]SFPGNILLKOJSJVKYLZN[Q[TZVXXUYRZNZKYHXGVFSF OIMLLOKSKWLY UXWUXRYNYJXH SFQGOJNLMOLSLXMZN[ Q[SZUWVUWRXNXIWGVF LXMVOUPURVSXT]U^V^W] T^U_V_ SXS_T`V`W]W\\", + "F^OFI[ PFJ[ QFK[ LFWFZG[I[KZNYOVPNP YGZIZKYNXO WFXGYIYKXNVP RPTQURWXXYYYZX WYXZYZ URVZW[Y[ZXZW F[N[ MFPG NFOH RFPH SFPG JZG[ JYH[ KYL[ JZM[", + "G^ZH[H\\F[L[JZHYGVFRFOGMIMLNNPPVSWUWXVZ NLONVRWT OGNINKOMUPWRXTXWWYVZS[O[LZKYJWJUI[JYKY", + "G]TFN[ UFO[ VFP[ MFKL ]F\\L MF]F K[S[ NFKL PFLI RFMG YF\\G ZF\\H [F\\I \\F\\L OZL[ OYM[ PYQ[ OZR[", + "F_NFKQJUJXKZN[R[UZWXXU\\G OFLQKUKYLZ PFMQLULYN[ KFSF YF_F LFOG MFNH QFOH RFOG ZF\\G ^F\\G", + "H\\NFNHOYO[ OGPX PFQW [GO[ LFSF XF^F MFNH QFPH RFOG YF[G ]F[G", + "E_MFMHKYK[ NGLX OFMW UFMWK[ UFUHSYS[ VGTX WFUW ]GUWS[ JFRF UFWF ZF`F KFNG LFMH PFNI QFNG [F]G _F]G", + "G]NFT[ OFU[ PFV[ [GIZ LFSF XF^F F[L[ Q[X[ MFOH QFPH RFPG YF[G ]F[G IZG[ IZK[ TZR[ TYS[ UYW[", + "G]MFQPN[ NFRPO[ OFSPP[ \\GSP KFRF YF_F K[S[ LFNG PFOH QFNG ZF\\G ^F\\G OZL[ OYM[ PYQ[ OZR[", + "G]ZFH[ [FI[ \\FJ[ \\FNFLL H[V[XU OFLL PFMI RFNG R[VZ T[WX U[XU", + "", + "", + "", + "", + "", + "", + "H\\JFR[ KFRX LFSX JFZFR[ LGYG LHYH", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "I]NPNOOOOQMQMONNPMTMVNWOXQXXYZZ[ VOWQWXXZ TMUNVPVXWZZ[[[ VRUSPTMULWLXMZP[S[UZVX NUMWMXNZ USQTOUNWNXOZP[", + "G\\LFL[MZOZ MGMY IFNFNZ NPONQMSMVNXPYSYUXXVZS[Q[OZNX WPXRXVWX SMUNVOWRWVVYUZS[ JFLG KFLH", + "H[WQWPVPVRXRXPVNTMQMNNLPKSKULXNZQ[S[VZXX MPLRLVMX QMONNOMRMVNYOZQ[", + "H]VFV[[[ WGWZ SFXFX[ VPUNSMQMNNLPKSKULXNZQ[S[UZVX MPLRLVMX QMONNOMRMVNYOZQ[ TFVG UFVH XYY[ XZZ[", + "H[MSXSXQWOVNSMQMNNLPKSKULXNZQ[S[VZXX WRWQVO MPLRLVMX VSVPUNSM QMONNOMRMVNYOZQ[", + "KYWHWGVGVIXIXGWFTFRGQHPKP[ RHQKQZ TFSGRIR[ MMVM M[U[ PZN[ PYO[ RYS[ RZT[", + "I\\XNYOZNYMXMVNUO QMONNOMQMSNUOVQWSWUVVUWSWQVOUNSMQM OONQNSOU UUVSVQUO QMPNOPOTPVQW SWTVUTUPTNSM NUMVLXLYM[N\\Q]U]X^Y_ N[Q\\U\\X] LYMZP[U[X\\Y^Y_XaUbObLaK_K^L\\O[ ObMaL_L^M\\O[", + "G^LFL[ MGMZ IFNFN[ NQOOPNRMUMWNXOYRY[ WOXRXZ UMVNWQW[ I[Q[ T[\\[ JFLG KFLH LZJ[ LYK[ NYO[ NZP[ WZU[ WYV[ YYZ[ YZ[[", + "LXQFQHSHSFQF RFRH QGSG QMQ[ RNRZ NMSMS[ N[V[ OMQN PMQO QZO[ QYP[ SYT[ SZU[", + "KXRFRHTHTFRF SFSH RGTG RMR^QaPb SNS]R` OMTMT]S`RaPbMbLaL_N_NaMaM` PMRN QMRO", + "G]LFL[ MGMZ IFNFN[ WNNW RSY[ RTX[ QTW[ TM[M I[Q[ T[[[ JFLG KFLH UMWN ZMWN LZJ[ LYK[ NYO[ NZP[ WYU[ VYZ[", + "LXQFQ[ RGRZ NFSFS[ N[V[ OFQG PFQH QZO[ QYP[ SYT[ SZU[", + "AcFMF[ GNGZ CMHMH[ HQIOJNLMOMQNROSRS[ QORRRZ OMPNQQQ[ SQTOUNWMZM\\N]O^R^[ \\O]R]Z ZM[N\\Q\\[ C[K[ N[V[ Y[a[ DMFN EMFO FZD[ FYE[ HYI[ HZJ[ QZO[ QYP[ SYT[ SZU[ \\ZZ[ \\Y[[ ^Y_[ ^Z`[", + "G^LML[ MNMZ IMNMN[ NQOOPNRMUMWNXOYRY[ WOXRXZ UMVNWQW[ I[Q[ T[\\[ JMLN KMLO LZJ[ LYK[ NYO[ NZP[ WZU[ WYV[ YYZ[ YZ[[", + "H\\QMNNLPKSKULXNZQ[S[VZXXYUYSXPVNSMQM MPLRLVMX WXXVXRWP QMONNOMRMVNYOZQ[ S[UZVYWVWRVOUNSM", + "G\\LMLb MNMa IMNMNb NPONQMSMVNXPYSYUXXVZS[Q[OZNX WPXRXVWX SMUNVOWRWVVYUZS[ IbQb JMLN KMLO LaJb L`Kb N`Ob NaPb", + "H\\VNVb WOWa UNWNXMXb VPUNSMQMNNLPKSKULXNZQ[S[UZVX MPLRLVMX QMONNOMRMVNYOZQ[ Sb[b VaTb V`Ub X`Yb XaZb", + "IZNMN[ ONOZ KMPMP[ WOWNVNVPXPXNWMUMSNQPPS K[S[ LMNN MMNO NZL[ NYM[ PYQ[ PZR[", + "J[WOXMXQWOVNTMPMNNMOMQNSPTUUWVXY NNMQ NRPSUTWU XVWZ MONQPRUSWTXVXYWZU[Q[OZNYMWM[NY", + "KZPHPVQYRZT[V[XZYX QHQWRY PHRFRWSZT[ MMVM", + "G^LMLVMYNZP[S[UZVYWW MNMWNY IMNMNWOZP[ WMW[\\[ XNXZ TMYMY[ JMLN KMLO YYZ[ YZ[[", + "I[LMR[ MMRY NMSY XNSYR[ JMQM TMZM KMNO PMNN VMXN YMXN", + "F^JMN[ KMNX LMOX RMOXN[ RMV[ SMVX RMTMWX ZNWXV[ GMOM WM]M HMKN NMLN XMZN \\MZN", + "H\\LMV[ MMW[ NMX[ WNMZ JMQM TMZM J[P[ S[Z[ KMMN PMNN UMWN YMWN MZK[ MZO[ VZT[ WZY[", + "H[LMR[ MMRY NMSY XNSYP_NaLbJbIaI_K_KaJaJ` JMQM TMZM KMNO PMNN VMXN YMXN", + "I[VML[ WMM[ XMN[ XMLMLQ L[X[XW MMLQ NMLP OMLO QMLN S[XZ U[XY V[XX W[XW", + "G^[MZQYTWXUZR[P[MZKXJUJSKPMNPMRMUNVOWQYXZZ[[\\[ ZMYQXTWVUYTZR[ LXKVKRLP P[NZMYLVLRMONNPM RMTNUOVQXXYZ[[", + "G\\QFNGMHLJKNKb NHMJLNLa QFOGNIMNMb QFSFVGWHXJXLWNVOSP PPTPWQXRYTYWXYWZT[Q[OZNYMW VHWJWLVN WRXTXWWY SFUGVIVMUOSP TPVQWSWXVZT[ KbMb", + "F\\HRINKMMMONPOQRRYSb IOKNMNOOPP HRIPKOMOOPPQQTRYRa XMWPVRTUSWR[Qb YMWQ ZMYOWRTVSXR[ XMZM QbSb", + "H\\SMQMNNLPKSKULXNZQ[S[VZXXYUYSXPVNSMPLNKMJMHNGPFSFWH MPLSLUMX WXXUXSWP QMONNOMRMVNYOZQ[ S[UZVYWVWRVOUNOKNJNIOHQGTGWH", + "I[SMUNVOWOVNSMQMMNLOLQMRQS SSQSMTKVKXMZP[S[VZXXWXVZ NNMOMQNR MULVLXMY QMONNONQORQS QSNTMVMXNZP[", + "I[QHRGRFQFPGPIQJTKXKYKYJXJUKSLPNNPMRLULWMYNZP[S\\U]V_VaUbSbRaR`S`Sa POOPNRMUMWNYOZ UKRMQNOQNTNWOYQ[S\\", + "G]JMKNLPL[ KMLNMPMZ HPINJMLMMNNPN[ UMVNWQWb WOXRXa NQOOPNRMUMWNXOYRYb L[N[ WbYb", + "F]IMJNKPKTLWMYNZQ[S[VZWYXWYRYOXJVGTFRFPGOIOKPMSOVP[Q JMKNLPLTMWNY VYWWXRXOWJVHTG GPHNIMKMLNMPMTNXOZQ[ S[UZVXWSWNVJUHSGQGOI", + "KZNMONPPPXQZS[U[WZXX OMPNQPQXRZ LPMNNMPMQNRPRXSZT[", + "G]JMKNLPL[ KMLNMPMZ HPINJMLMMNNPN[ SOUNWNXOXPZPZNXMVMTNQQOTNW XNYOYP PSQSWYYYZX TWWZYZ RTUZV[X[YZZX L[N[", + "H\\JGKFMFOGQIXXYZZ[ OHPIWXXY MFNGOIVXXZZ[[[ RMJZJ[K[RM", + "G]KMKb LNLa MMMb VMVXWZX[Z[[Z\\X WNWXXZY[ XMXXYZZ[ MXNZP[R[TZUYVW KMMM VMXM KbMb", + "G]JMKNLPMTN[ KMLNMPNTOZ HPINJMLMMNNPOTPZ VVWTXQXMYMZNYQXSVVTXQZN[ XRYOYM", + "JZPGSFRFPGOHOIPJSKVLWKVJSKPLNMMOMQNRPSSTVUWTVSSTOUMVLXLZM[O\\S]U^V_VaTbRbOaPaRb OMNONQOR NVMXMZN[ VKSKQLPMOOOQQSST VTSTPUOVNXNZP\\S]", + "H\\QMNNLPKSKULXNZQ[S[VZXXYUYSXPVNSMQM MPLRLVMX WXXVXRWP QMONNOMRMVNYOZQ[ S[UZVYWVWRVOUNSM", + "G]IQJOKNMM[M KOMNZN IQJPLO[O OONZM[LZMWOO UOVZW[XZWWUO [M[O OOMZ UOWZ", + "G\\QMNNLPKTKb MPLTLa QMONNOMSMb MWNYOZQ[S[VZXXYUYSXPVNSMQM WXXVXRWP S[UZVYWVWRVOUNSM KbMb", + "G]PMMNKPJSJUKXMZP[R[UZWXXUXSWPUNRM LPKRKVLX VXWVWRVP PMNNMOLRLVMYNZP[ R[TZUYVVVRUOTNRM RMZO[N[MPM RMZN", + "H\\JQKOLNNMZM LONNYN JQKPMOZO ROQZR[SZRO ZMZO RORZ", + "G\\JMKNLPLUMXOZQ[S[UZWXXVYRYNXMWMXPXSWWUZ KMLNMPMUNX WMXNXO HPINJMLMMNNPNVOYQ[", + "G]RQQNPMNMLNKOJRJUKXMZP[T[WZYXZUZRYOXNVMTMSNRQ LOKRKULX XXYUYRXO NMMNLQLVMYNZP[ T[VZWYXVXQWNVM RQQb RQRa RQSb QbSb", + "H\\LMMNNPT_VaXbZb[a NOOPU_V` INJMLMNNPPV_WaXb VSXPYMZMYOVSN\\K`JbKbL_N\\", + "F]HNINJPJUKXMZP[T[VZXXYVZRZNYMXMYPYSXWVZ JNKPKULX XMYNYO GPHNIMJMKNLPLVMYNZP[ QFSb RGRa SFQb QFSF QbSb", + "F^NMLNJPISIWJYKZM[O[QZRYSWSTRSQTQWRYSZU[W[YZZY[W[SZPXNVM KPJSJWKY RTRX YYZWZSYP NMLOKRKWLZM[ W[XZYWYRXOVM", + "G]WMUTUXVZW[Y[[Y\\W XMVTVZ WMYMWTVX UTUQTNRMPMMNKQJTJVKYLZN[P[RZSYTWUT NNLQKTKWLY PMNOMQLTLWMZN[", + "I\\PFNMMSMWNYOZQ[S[VZXWYTYRXOWNUMSMQNPOOQNT QFOMNQNWOZ VYWWXTXQWO MFRFPMNT S[UYVWWTWQVNUM NFQG OFPH", + "I[WQWPVPVRXRXPWNUMRMONMQLTLVMYNZP[R[UZWW OONQMTMWNY RMPOOQNTNWOZP[", + "G]YFVQUUUXVZW[Y[[Y\\W ZFWQVUVZ VF[FWTVX UTUQTNRMPMMNKQJTJVKYLZN[P[RZSYTWUT MOLQKTKWLY PMNOMQLTLWMZN[ WFZG XFYH", + "I[MVQUTTWRXPWNUMRMONMQLTLVMYNZP[R[UZWX OONQMTMWNY RMPOOQNTNWOZP[", + "JZZHZGYGYI[I[GZFXFVGTISKRNQRO[N^M`Kb TJSMRRP[O^ XFVHUJTMSRQZP]O_MaKbIbHaH_J_JaIaI` NMYM", + "H]XMT[S^QaOb YMU[S_ XMZMV[T_RaObLbJaI`I^K^K`J`J_ VTVQUNSMQMNNLQKTKVLYMZO[Q[SZTYUWVT NOMQLTLWMY QMOONQMTMWNZO[", + "G]OFI[K[ PFJ[ LFQFK[ MTOPQNSMUMWNXPXSVX WNWRVVVZ WPUUUXVZW[Y[[Y\\W MFPG NFOH", + "KXTFTHVHVFTF UFUH TGVG LQMOOMQMRNSPSSQX RNRRQVQZ RPPUPXQZR[T[VYWW", + "KXUFUHWHWFUF VFVH UGWG MQNOPMRMSNTPTSRZQ]P_NaLbJbIaI_K_KaJaJ` SNSSQZP]O_ SPRTP[O^N`Lb", + "G]OFI[K[ PFJ[ LFQFK[ YOYNXNXPZPZNYMWMUNQROS MSOSQTRUTYUZWZ QUSYTZ OSPTRZS[U[WZYW MFPG NFOH", + "LXTFQQPUPXQZR[T[VYWW UFRQQUQZ QFVFRTQX RFUG SFTH", + "@cAQBODMFMGNHPHSF[ GNGSE[ GPFTD[F[ HSJPLNNMPMRNSPSSQ[ RNRSP[ RPQTO[Q[ SSUPWNYM[M]N^P^S\\X ]N]R\\V\\Z ]P[U[X\\Z][_[aYbW", + "F^GQHOJMLMMNNPNSL[ MNMSK[ MPLTJ[L[ NSPPRNTMVMXNYPYSWX XNXRWVWZ XPVUVXWZX[Z[\\Y]W", + "H\\QMNNLQKTKVLYMZP[S[VZXWYTYRXOWNTMQM NOMQLTLWMY VYWWXTXQWO QMOONQMTMWNZP[ S[UYVWWTWQVNTM", + "G]HQIOKMMMNNOPOSNWKb NNNSMWJb NPMTIb OTPQQORNTMVMXNYOZRZTYWWZT[R[PZOWOT XOYQYTXWWY VMWNXQXTWWVYT[ FbNb JaGb J`Hb K`Lb JaMb", + "G\\WMQb XMRb WMYMSb UTUQTNRMPMMNKQJTJVKYLZN[P[RZSYTWUT MOLQKTKWLY PMNOMQLTLWMZN[ NbVb RaOb R`Pb S`Tb RaUb", + "I[JQKOMMOMPNQPQTO[ PNPTN[ PPOTM[O[ YOYNXNXPZPZNYMWMUNSPQT", + "J[XPXOWOWQYQYOXNUMRMONNONQOSQTTUVVWX ONNQ ORQSTTVU WVVZ NOOQQRTSVTWVWXVZS[P[MZLYLWNWNYMYMX", + "KYTFQQPUPXQZR[T[VYWW UFRQQUQZ TFVFRTQX NMXM", + "F^GQHOJMLMMNNPNSLX MNMRLVLZ MPKUKXLZN[P[RZTXVU XMVUVXWZX[Z[\\Y]W YMWUWZ XMZMXTWX", + "H\\IQJOLMNMONPPPSNX ONORNVNZ OPMUMXNZP[R[TZVXXUYQYMXMXNYP", + "CaDQEOGMIMJNKPKSIX JNJRIVIZ JPHUHXIZK[M[OZQXRU TMRURXSZU[W[YZ[X]U^Q^M]M]N^P UMSUSZ TMVMTTSX", + "G]JQLNNMPMRNSPSR PMQNQRPVOXMZK[I[HZHXJXJZIZIY RORRQVQY ZOZNYNYP[P[NZMXMVNTPSRRVRZS[ PVPXQZS[U[WZYW", + "G]HQIOKMMMNNOPOSMX NNNRMVMZ NPLULXMZO[Q[SZUXWT YMU[T^RaPb ZMV[T_ YM[MW[U_SaPbMbKaJ`J^L^L`K`K_", + "H\\YMXOVQNWLYK[ XOOOMPLR VORNONNO VORMOMMOLR LYUYWXXV NYRZUZVY NYR[U[WYXV", + "", + "", + "", + "", + "", + "", + "H\\WQVOUNSMQMNNLPKSKULXNZQ[S[VZWYXWYSYNXJWHVGSFQFNGMHNHOGQF MPLRLVMX VYWWXSXNWJVH QMONNOMRMVNYOZQ[ S[UZVXWTWMVIUGSF", + "I[UMWNXOYOXNUMRMONMPLSLUMXOZR[U[XZYYXYWZU[ NPMSMUNX RMPNOONRNVOYPZR[ NTTUUTTSNT NTTT", + "H\\QFNGLJKOKRLWNZQ[S[VZXWYRYOXJVGSFQF NHMJLNLSMWNY VYWWXSXNWJVH QFOGNIMNMSNXOZQ[ S[UZVXWSWNVIUGSF LPXQ LQXP", + "G]PMMNKPJSJUKXMZP[T[WZYXZUZSYPWNTMPM LPKSKULX XXYUYSXP PMNNMOLRLVMYNZP[T[VZWYXVXRWOVNTM QFSb RGRa SFQb QFSF QbSb", + "H\\TMVNXPYPYOWNTMPMMNLOKQKSLUNWPXRYSZT\\T^S_Q_O^P^Q_ MOLQLSMUOW PMNNMPMSNURY YPXO", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "NV", + "JZ", + "H\\QFNGLJKOKRLWNZQ[S[VZXWYRYOXJVGSFQF NHMJLNLSMWNY VYWWXSXNWJVH QFOGNIMNMSNXOZQ[ S[UZVXWSWNVIUGSF", + "H\\QHQ[ RHRZ SFS[ SFPINJ M[W[ QZO[ QYP[ SYT[ SZU[", + "H\\LJLKMKMJLJ LIMINJNKMLLLKKKJLHMGPFTFWGXHYJYLXNUPPRNSLUKXK[ WHXJXLWN TFVGWJWLVNTPPR KYLXNXSYWYYX NXSZWZXY NXS[W[XZYXYV", + "H\\LJLKMKMJLJ LIMINJNKMLLLKKKJLHMGPFTFWGXIXLWNTO VGWIWLVN SFUGVIVLUNSO QOTOVPXRYTYWXYWZT[P[MZLYKWKVLUMUNVNWMXLX WRXTXWWY SOUPVQWTWWVZT[ LVLWMWMVLV", + "H\\SIS[ THTZ UFU[ UFJUZU P[X[ SZQ[ SYR[ UYV[ UZW[", + "H\\MFKPMNPMSMVNXPYSYUXXVZS[P[MZLYKWKVLUMUNVNWMXLX WPXRXVWX SMUNVOWRWVVYUZS[ LVLWMWMVLV MFWF MGUG MHQHUGWF", + "H\\VIVJWJWIVI WHVHUIUJVKWKXJXIWGUFRFOGMILKKOKULXNZQ[S[VZXXYUYTXQVOSNQNOONPMR NIMKLOLUMXNY WXXVXSWQ RFPGOHNJMNMUNXOZQ[ S[UZVYWVWSVPUOSN", + "H\\KFKL YFYIXLTQSSRWR[ SRRTQWQ[ XLSQQTPWP[R[ KJLHNFPFUIWIXHYF MHNGPGRH KJLINHPHUI", + "H\\PFMGLILLMNPOTOWNXLXIWGTFPF NGMIMLNN VNWLWIVG PFOGNINLONPO TOUNVLVIUGTF POMPLQKSKWLYMZP[T[WZXYYWYSXQWPTO MQLSLWMY WYXWXSWQ PONPMSMWNZP[ T[VZWWWSVPTO", + "H\\MWMXNXNWMW WOVQURSSQSNRLPKMKLLINGQFSFVGXIYLYRXVWXUZR[O[MZLXLWMVNVOWOXNYMY MPLNLKMI VHWIXLXRWVVX QSORNQMNMKNHOGQF SFUGVIWLWSVWUYTZR[", + "MXRXQYQZR[S[TZTYSXRX RYRZSZSYRY", + "MXTZS[R[QZQYRXSXTYT\\S^Q_ RYRZSZSYRY S[T\\ TZS^", + "MXRMQNQORPSPTOTNSMRM RNROSOSNRN RXQYQZR[S[TZTYSXRX RYRZSZSYRY", + "MXRMQNQORPSPTOTNSMRM RNROSOSNRN TZS[R[QZQYRXSXTYT\\S^Q_ RYRZSZSYRY S[T\\ TZS^", + "MXRFQGQIRQ RFRTST RFSFST SFTGTISQ RXQYQZR[S[TZTYSXRX RYRZSZSYRY", + "I\\MKMJNJNLLLLJMHNGPFTFWGXHYJYLXNWOSQ WHXIXMWN TFVGWIWMVOUP RQRTSTSQRQ RXQYQZR[S[TZTYSXRX RYRZSZSYRY", + "MXTFRGQIQLRMSMTLTKSJRJQK RKRLSLSKRK RGQK QIRJ", + "MXTHSIRIQHQGRFSFTGTJSLQM RGRHSHSGRG SITJ THSL", + "E_[O[NZNZP\\P\\N[MZMYNXPVUTXRZP[L[JZIXIUJSPORMSKSIRGPFNGMIMLNOPRTWWZY[[[\\Y\\X KZJXJUKSLR RMSI SKRG NGMK NNPQTVWYYZ N[LZKXKULSPO MINMQQUVXYZZ[Z\\Y", + "H\\PBP_ TBT_ XKXJWJWLYLYJXHWGTFPFMGKIKLLNOPURWSXUXXWZ LLMNOOUQWRXT MGLILKMMONUPXRYTYWXYWZT[P[MZLYKWKUMUMWLWLV", + "G^[BIbJb [B\\BJb", + "KYUBSDQGOKNPNTOYQ]S`Ub QHPKOOOUPYQ\\ SDRFQIPOPUQ[R^S`", + "KYOBQDSGUKVPVTUYS]Q`Ob SHTKUOUUTYS\\ QDRFSITOTUS[R^Q`", + "JZRFQGSQRR RFRR RFSGQQRR MINIVOWO MIWO MIMJWNWO WIVINOMO WIMO WIWJMNMO", + "F_JQ[Q[R JQJR[R", + "F_RIRZSZ RISISZ JQ[Q[R JQJR[R", + "F_JM[M[N JMJN[N JU[U[V JUJV[V", + "NWSFRGRM SGRM SFTGRM", + "I[NFMGMM NGMM NFOGMM WFVGVM WGVM WFXGVM", + "KYQFOGNINKOMQNSNUMVKVIUGSFQF QFNIOMSNVKUGQF SFOGNKQNUMVISF", + "F^ZIJRZ[ ZIZJLRZZZ[", + "F^JIZRJ[ JIJJXRJZJ[", + "G^OFObPb OFPFPb UFUbVb UFVFVb JP[P[Q JPJQ[Q JW[W[X JWJX[X", + "F^[FYGVHSHPGNFLFJGIIIKKMMMOLPJPHNF [FH[ [FI[ [FJ[ YTWTUUTWTYV[X[ZZ[X[VYT OGLFIIJLMMPJOG NFJGIK KMOLPH ZUWTTWUZX[[XZU YTUUTY V[ZZ[V H[J[", + "E`VNULSKQKOLNMMOMRNTOUQVSVUUVS OMNONROT QKPLOOORPUQV VKVSWUYVZV\\U]R]O\\L[JYHWGTFQFNGLHJJILHOHRIUJWLYNZQ[T[WZYYXYWZ WLWSXU VKXKXSYUZV", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "NV", + "JZ", + "H]TFQGOIMLLOKSKVLYMZO[Q[TZVXXUYRZNZKYHXGVFTF QHOJNLMOLSLWMY TYVWWUXRYNYJXH TFRGPJOLNOMSMXNZO[ Q[SZUWVUWRXNXIWGVF", + "H]TJO[Q[ WFUJP[ WFQ[ WFTIQKOL TJRKOL", + "H]OKOJPJPLNLNJOHPGSFVFYGZIZKYMWOMUKWI[ XGYIYKXMVOSQ VFWGXIXKWMUOMU JYKXMXRYWYXX MXRZWZ MXR[U[WZXXXW", + "H]OKOJPJPLNLNJOHPGSFVFYGZIZKYMXNVOSP XGYIYKXMWN VFWGXIXKWMUOSP QPSPVQWRXTXWWYUZR[O[LZKYJWJULULWKWKV VRWTWWVY SPUQVSVWUYTZR[", + "H]WJR[T[ ZFXJS[ ZFT[ ZFJUZU", + "H]QFLP QF[F QGYG PHUHYG[F LPMOPNSNVOWPXRXUWXUZQ[N[LZKYJWJULULWKWKV VPWRWUVXTZ SNUOVQVUUXSZQ[", + "H]YJYIXIXKZKZIYGWFTFQGOIMLLOKSKVLYMZO[R[UZWXXVXSWQVPTOQOOPNQMS PINLMOLSLWMY VXWVWSVQ TFRGPJOLNOMSMXNZO[ R[TZUYVVVRUPTO", + "H]NFLL [FZIXLTQRTQWP[ RSPWO[ XLRRPUOWN[P[ MIPFRFWI OGRGWI MIOHRHWIYIZH[F", + "H]SFPGOHNJNMOOQPTPWOYNZLZIYGWFSF UFPG PHOJONPO OORP SPWO XNYLYIXG YGUF SFQHPJPNQP TPVOWNXLXHWF QPMQKSJUJXKZN[R[VZWYXWXTWRVQTP RPMQ NQLSKUKXLZ KZP[VZ VYWWWTVR VQSP QPOQMSLULXMZN[ R[TZUYVWVSUQTP", + "H]XNWPVQTRQROQNPMNMKNIPGSFVFXGYHZKZNYRXUVXTZQ[N[LZKXKVMVMXLXLW OPNNNKOI XHYJYNXRWUUX QRPQOOOKPHQGSF VFWGXIXNWRVUUWSZQ[", + "MXPXOYOZP[Q[RZRYQXPX PYPZQZQYPY", + "MXQ[P[OZOYPXQXRYR[Q]P^N_ PYPZQZQYPY Q[Q\\P^", + "MXSMRNROSPTPUOUNTMSM SNSOTOTNSN PXOYOZP[Q[RZRYQXPX PYPZQZQYPY", + "MXSMRNROSPTPUOUNTMSM SNSOTOTNSN Q[P[OZOYPXQXRYR[Q]P^N_ PYPZQZQYPY Q[Q\\P^", + "MXVFUFTGRT VGUGRT VGVHRT VFWGWHRT PXOYOZP[Q[RZRYQXPX PYPZQZQYPY", + "H]OKOJPJPLNLNJOHPGSFWFZG[I[KZMYNWOSPQQQSSTTT UFZG YGZIZKYMXNVO WFXGYIYKXMWNSPRQRSST PXOYOZP[Q[RZRYQXPX PYPZQZQYPY", + "MXWFUGTHSJSLTMUMVLVKUJTJ UGTITJ TKTLULUKTK", + "MXVIUITHTGUFVFWGWIVKULSM UGUHVHVGUG VIVJUL", + "E_\\O\\N[N[P]P]N\\M[MYNWPRXPZN[K[HZGXGVHTISKRPPROTMUKUITGRFPGOIOLPRQURWTZV[X[YYYX L[HZ IZHXHVITJSLR PPQSTYVZ K[JZIXIVJTKSMRRO OLPOQRSVUYWZXZYY", + "H]TBL_ YBQ_ ZKZJYJYL[L[JZHYGVFRFOGMIMLNNPPVSWUWXVZ NLONVRWT OGNINKOMUPWRXTXWWYVZS[O[LZKYJWJULULWKWKV", + "G^_BEbFb _B`BFb", + "JZZBXCUERHPKNOMSMXN\\O_Qb SHQKOONTN\\ ZBWDTGRJQLPOOSN\\ NTO]P`Qb", + "JZSBUEVHWLWQVUTYR\\O_LaJb VHVPUUSYQ\\ SBTDUGVP VHUQTUSXRZP]M`Jb", + "J[TFSGUQTR TFTR TFUGSQTR OIPIXOYO OIYO OIOJYNYO YIXIPOOO YIOO YIYJONOO", + "F_JQ[Q[R JQJR[R", + "F_RIRZSZ RISISZ JQ[Q[R JQJR[R", + "F_JM[M[N JMJN[N JU[U[V JUJV[V", + "MWUFTGRM UGRM UFVGRM", + "H\\PFOGMM PGMM PFQGMM ZFYGWM ZGWM ZF[GWM", + "KZSFQGPIPKQMSNUNWMXKXIWGUFSF SFPIQMUNXKWGSF UFQGPKSNWMXIUF", + "F^ZIJRZ[ ZIZJLRZZZ[", + "F^JIZRJ[ JIJJXRJZJ[", + "G^SFKbLb SFTFLb YFQbRb YFZFRb KP\\P\\Q KPKQ\\Q IWZWZX IWIXZX", + "E^^F\\GXHUHQGOFMFKGJIJKLMNMPLQJQHOF ^FE[ ^FF[ ^FG[ XTVTTUSWSYU[W[YZZXZVXT PGMFJIKLNMQJPG OFKGJK LMPLQH YUVTSWTZW[ZXYU XTTUSY U[YZZV E[G[", + "E`UQUNTLRKPKNLMMLPLSMUOVQVSUTTUQ OLNMMPMSNU RKPLOMNPNSOUPV VKUQUSVUXVZV\\U]R]O\\L[JYHWGTFQFNGLHJJILHOHRIUJWLYNZQ[T[WZYYXYWZ WKVQVSWU VKXKWQWSXUZV", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + 0 }; +} +/* End of file. */ diff --git a/canvas/hover_prelight.cpp b/canvas/hover_prelight.cpp new file mode 100644 index 000000000..04ed01458 --- /dev/null +++ b/canvas/hover_prelight.cpp @@ -0,0 +1,34 @@ +#include "canvas.hpp" +#include +#include +#include + +namespace horizon { + + void CanvasGL::hover_prelight_update(GdkEventMotion *motion_event) { + if(!selection_allowed) + return; + if(selection_mode != CanvasGL::SelectionMode::HOVER) + return; + gdouble x,y; + gdk_event_get_coords((GdkEvent*)motion_event, &x, &y); + auto c = screen2canvas({(float)x,(float)y}); + float area_min = 1e99; + int area_min_i = -1; + unsigned int i = 0; + for(auto &it: selectables.items) { + it.flags=0 ; + if(it.inside(c, 10/scale) && selection_filter.can_select(selectables.items_ref[i])) { + if(it.area() +#include + +namespace horizon { + void Canvas::img_line(const Coordi &p0, const Coordi &p1, const uint64_t width, int layer) { + if(!img_mode) + return; + Polygon poly(UUID::random()); + poly.layer = layer; + auto v = p1-p0; + Coord vn = v; + if(vn.mag_sq()>0) { + vn = vn/sqrt(vn.mag_sq()); + vn *= width/2; + } + else { + vn = {(double)width/2, 0}; + } + Coordi vni(-vn.y, vn.x); + poly.vertices.emplace_back(p0+vni); + auto &a0 = poly.vertices.back(); + a0.type = Polygon::Vertex::Type::ARC; + a0.arc_center = p0; + poly.vertices.emplace_back(p0-vni); + + poly.vertices.emplace_back(p1-vni); + auto &a1 = poly.vertices.back(); + a1.type = Polygon::Vertex::Type::ARC; + a1.arc_center = p1; + poly.vertices.emplace_back(p1+vni); + + //poly.vertices.push_back(p0+vn, p0-vn); + + auto polyr = poly.remove_arcs(); + + img_polygon(polyr); + } + + void Canvas::img_text_layer(int l) { + img_text_last_layer = l; + } + + void Canvas::img_text_line(const Coordi &p0, const Coordi &p1, uint64_t width) { + if(img_text_last_layer != 10000) + img_line(p0, p1, width, img_text_last_layer); + } + +} diff --git a/canvas/layer_display.hpp b/canvas/layer_display.hpp new file mode 100644 index 000000000..146e11e51 --- /dev/null +++ b/canvas/layer_display.hpp @@ -0,0 +1,16 @@ +#pragma once +#include "common.hpp" + +namespace horizon { + class LayerDisplay { + public: + enum class Mode {OUTLINE, HATCH, FILL}; + LayerDisplay(bool vi, Mode mo, const Color &c): + visible(vi), mode(mo), color(c){} + LayerDisplay() {} + + bool visible=true; + Mode mode=Mode::FILL; + Color color; + }; +} diff --git a/canvas/pan.cpp b/canvas/pan.cpp new file mode 100644 index 000000000..122e994b3 --- /dev/null +++ b/canvas/pan.cpp @@ -0,0 +1,97 @@ +#include "canvas.hpp" +#include +#include +#include + +namespace horizon { + void CanvasGL::pan_drag_begin(GdkEventButton* button_event) { + gdouble x,y; + gdk_event_get_coords((GdkEvent*)button_event, &x, &y); + if(button_event->button==2) { //inside of grid and middle mouse button + pan_dragging = true; + pan_pointer_pos_orig = {(float)x, (float)y}; + pan_offset_orig = offset; + } + } + + void CanvasGL::pan_drag_end(GdkEventButton* button_event) { + pan_dragging = false; + } + + void CanvasGL::pan_drag_move(GdkEventMotion *motion_event) { + gdouble x,y; + gdk_event_get_coords((GdkEvent*)motion_event, &x, &y); + if(warped) { + pan_pointer_pos_orig = {(float)x, (float)y}; + pan_offset_orig = offset; + warped = false; + return; + } + if(pan_dragging) { + if(x>get_allocated_width() || x<0 || y>get_allocated_height() || y<0) { + auto dev = gdk_event_get_device((GdkEvent*)motion_event); + auto scr = gdk_event_get_screen((GdkEvent*)motion_event); + gdouble rx, ry; + gdk_event_get_root_coords((GdkEvent*)motion_event, &rx, &ry); + if(x>get_allocated_width()) { + gdk_device_warp(dev, scr, rx-get_allocated_width(), ry); + } + else if(x<0) { + gdk_device_warp(dev, scr, rx+get_allocated_width(), ry); + } + else if(y>get_allocated_height()) { + gdk_device_warp(dev, scr, rx, ry-get_allocated_height()); + } + else if(y<0) { + gdk_device_warp(dev, scr, rx, ry+get_allocated_height()); + } + warped = true; + } + offset = pan_offset_orig + Coordf(x,y) - pan_pointer_pos_orig; + queue_draw(); + } + } + + void CanvasGL::pan_drag_move(GdkEventScroll *scroll_event) { + gdouble dx,dy; + gdk_event_get_scroll_deltas((GdkEvent*)scroll_event, &dx, &dy); + + offset.x += dx*50; + offset.y += dy*50; + queue_draw(); + } + + void CanvasGL::pan_zoom(GdkEventScroll *scroll_event, bool to_cursor) { + gdouble x,y; + if(to_cursor) { + gdk_event_get_coords((GdkEvent*)scroll_event, &x, &y); + } + else { + x = width/2; + y = height/2; + } + float sc = this->scale; + + float scale_new=1; + if(scroll_event->direction == GDK_SCROLL_UP) { + scale_new = sc*1.5; + } + else if(scroll_event->direction == GDK_SCROLL_DOWN) { + scale_new = sc/1.5; + } + else if(scroll_event->direction == GDK_SCROLL_SMOOTH) { + gdouble sx, sy; + gdk_event_get_scroll_deltas((GdkEvent*)scroll_event, &sx, &sy); + scale_new = sc * powf(1.5, -sy); + } + if(scale_new < 1e-7 || scale_new > 1e-3) { + return; + } + this->scale = scale_new; + float xp = (x-offset.x)/sc; + float yp = -(y-offset.y)/sc; + offset.x += xp*(sc-scale_new); + offset.y += yp*(scale_new-sc); + queue_draw(); + } +} diff --git a/canvas/polypartition/polypartition.cpp b/canvas/polypartition/polypartition.cpp new file mode 100644 index 000000000..831b2c61f --- /dev/null +++ b/canvas/polypartition/polypartition.cpp @@ -0,0 +1,1552 @@ +//Copyright (C) 2011 by Ivan Fratric +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in +//all copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +//THE SOFTWARE. + + +#include +#include +#include +#include +#include +#include + +using namespace std; + +#include "polypartition.h" + +#define TPPL_VERTEXTYPE_REGULAR 0 +#define TPPL_VERTEXTYPE_START 1 +#define TPPL_VERTEXTYPE_END 2 +#define TPPL_VERTEXTYPE_SPLIT 3 +#define TPPL_VERTEXTYPE_MERGE 4 + +TPPLPoly::TPPLPoly() { + hole = false; + numpoints = 0; + points = NULL; +} + +TPPLPoly::~TPPLPoly() { + if(points) delete [] points; +} + +void TPPLPoly::Clear() { + if(points) delete [] points; + hole = false; + numpoints = 0; + points = NULL; +} + +void TPPLPoly::Init(long numpoints) { + Clear(); + this->numpoints = numpoints; + points = new TPPLPoint[numpoints]; +} + +void TPPLPoly::Triangle(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3) { + Init(3); + points[0] = p1; + points[1] = p2; + points[2] = p3; +} + +TPPLPoly::TPPLPoly(const TPPLPoly &src) { + hole = src.hole; + numpoints = src.numpoints; + points = new TPPLPoint[numpoints]; + memcpy(points, src.points, numpoints*sizeof(TPPLPoint)); +} + +TPPLPoly& TPPLPoly::operator=(const TPPLPoly &src) { + Clear(); + hole = src.hole; + numpoints = src.numpoints; + points = new TPPLPoint[numpoints]; + memcpy(points, src.points, numpoints*sizeof(TPPLPoint)); + return *this; +} + +int TPPLPoly::GetOrientation() { + long i1,i2; + tppl_float area = 0; + for(i1=0; i10) return TPPL_CCW; + if(area<0) return TPPL_CW; + return 0; +} + +void TPPLPoly::SetOrientation(int orientation) { + int polyorientation = GetOrientation(); + if(polyorientation&&(polyorientation!=orientation)) { + Invert(); + } +} + +void TPPLPoly::Invert() { + long i; + TPPLPoint *invpoints = NULL; + + invpoints = new TPPLPoint[numpoints]; + for(i=0;i0) return 0; + if(dot21*dot22>0) return 0; + + return 1; +} + +//removes holes from inpolys by merging them with non-holes +int TPPLPartition::RemoveHoles(list *inpolys, list *outpolys) { + list polys; + list::iterator holeiter,polyiter,iter,iter2; + long i,i2,holepointindex,polypointindex; + TPPLPoint holepoint,polypoint,bestpolypoint; + TPPLPoint linep1,linep2; + TPPLPoint v1,v2; + TPPLPoly newpoly; + bool hasholes; + bool pointvisible; + bool pointfound; + + //check for trivial case (no holes) + hasholes = false; + for(iter = inpolys->begin(); iter!=inpolys->end(); iter++) { + if(iter->IsHole()) { + hasholes = true; + break; + } + } + if(!hasholes) { + for(iter = inpolys->begin(); iter!=inpolys->end(); iter++) { + outpolys->push_back(*iter); + } + return 1; + } + + polys = *inpolys; + + while(1) { + //find the hole point with the largest x + hasholes = false; + for(iter = polys.begin(); iter!=polys.end(); iter++) { + if(!iter->IsHole()) continue; + + if(!hasholes) { + hasholes = true; + holeiter = iter; + holepointindex = 0; + } + + for(i=0; i < iter->GetNumPoints(); i++) { + if(iter->GetPoint(i).x > holeiter->GetPoint(holepointindex).x) { + holeiter = iter; + holepointindex = i; + } + } + } + if(!hasholes) break; + holepoint = holeiter->GetPoint(holepointindex); + + pointfound = false; + for(iter = polys.begin(); iter!=polys.end(); iter++) { + if(iter->IsHole()) continue; + for(i=0; i < iter->GetNumPoints(); i++) { + if(iter->GetPoint(i).x <= holepoint.x) continue; + if(!InCone(iter->GetPoint((i+iter->GetNumPoints()-1)%(iter->GetNumPoints())), + iter->GetPoint(i), + iter->GetPoint((i+1)%(iter->GetNumPoints())), + holepoint)) + continue; + polypoint = iter->GetPoint(i); + if(pointfound) { + v1 = Normalize(polypoint-holepoint); + v2 = Normalize(bestpolypoint-holepoint); + if(v2.x > v1.x) continue; + } + pointvisible = true; + for(iter2 = polys.begin(); iter2!=polys.end(); iter2++) { + if(iter2->IsHole()) continue; + for(i2=0; i2 < iter2->GetNumPoints(); i2++) { + linep1 = iter2->GetPoint(i2); + linep2 = iter2->GetPoint((i2+1)%(iter2->GetNumPoints())); + if(Intersects(holepoint,polypoint,linep1,linep2)) { + pointvisible = false; + break; + } + } + if(!pointvisible) break; + } + if(pointvisible) { + pointfound = true; + bestpolypoint = polypoint; + polyiter = iter; + polypointindex = i; + } + } + } + + if(!pointfound) return 0; + + newpoly.Init(holeiter->GetNumPoints() + polyiter->GetNumPoints() + 2); + i2 = 0; + for(i=0;i<=polypointindex;i++) { + newpoly[i2] = polyiter->GetPoint(i); + i2++; + } + for(i=0;i<=holeiter->GetNumPoints();i++) { + newpoly[i2] = holeiter->GetPoint((i+holepointindex)%holeiter->GetNumPoints()); + i2++; + } + for(i=polypointindex;iGetNumPoints();i++) { + newpoly[i2] = polyiter->GetPoint(i); + i2++; + } + + polys.erase(holeiter); + polys.erase(polyiter); + polys.push_back(newpoly); + } + + for(iter = polys.begin(); iter!=polys.end(); iter++) { + outpolys->push_back(*iter); + } + + return 1; +} + +bool TPPLPartition::IsConvex(TPPLPoint& p1, TPPLPoint& p2, TPPLPoint& p3) { + tppl_float tmp; + tmp = (p3.y-p1.y)*(p2.x-p1.x)-(p3.x-p1.x)*(p2.y-p1.y); + if(tmp>0) return 1; + else return 0; +} + +bool TPPLPartition::IsReflex(TPPLPoint& p1, TPPLPoint& p2, TPPLPoint& p3) { + tppl_float tmp; + tmp = (p3.y-p1.y)*(p2.x-p1.x)-(p3.x-p1.x)*(p2.y-p1.y); + if(tmp<0) return 1; + else return 0; +} + +bool TPPLPartition::IsInside(TPPLPoint& p1, TPPLPoint& p2, TPPLPoint& p3, TPPLPoint &p) { + if(IsConvex(p1,p,p2)) return false; + if(IsConvex(p2,p,p3)) return false; + if(IsConvex(p3,p,p1)) return false; + return true; +} + +bool TPPLPartition::InCone(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3, TPPLPoint &p) { + bool convex; + + convex = IsConvex(p1,p2,p3); + + if(convex) { + if(!IsConvex(p1,p2,p)) return false; + if(!IsConvex(p2,p3,p)) return false; + return true; + } else { + if(IsConvex(p1,p2,p)) return true; + if(IsConvex(p2,p3,p)) return true; + return false; + } +} + +bool TPPLPartition::InCone(PartitionVertex *v, TPPLPoint &p) { + TPPLPoint p1,p2,p3; + + p1 = v->previous->p; + p2 = v->p; + p3 = v->next->p; + + return InCone(p1,p2,p3,p); +} + +void TPPLPartition::UpdateVertexReflexity(PartitionVertex *v) { + PartitionVertex *v1 = NULL,*v3 = NULL; + v1 = v->previous; + v3 = v->next; + v->isConvex = !IsReflex(v1->p,v->p,v3->p); +} + +void TPPLPartition::UpdateVertex(PartitionVertex *v, PartitionVertex *vertices, long numvertices) { + long i; + PartitionVertex *v1 = NULL,*v3 = NULL; + TPPLPoint vec1,vec3; + + v1 = v->previous; + v3 = v->next; + + v->isConvex = IsConvex(v1->p,v->p,v3->p); + + vec1 = Normalize(v1->p - v->p); + vec3 = Normalize(v3->p - v->p); + v->angle = vec1.x*vec3.x + vec1.y*vec3.y; + + if(v->isConvex) { + v->isEar = true; + for(i=0;ip.x)&&(vertices[i].p.y==v->p.y)) continue; + if((vertices[i].p.x==v1->p.x)&&(vertices[i].p.y==v1->p.y)) continue; + if((vertices[i].p.x==v3->p.x)&&(vertices[i].p.y==v3->p.y)) continue; + if(IsInside(v1->p,v->p,v3->p,vertices[i].p)) { + v->isEar = false; + break; + } + } + } else { + v->isEar = false; + } +} + +//triangulation by ear removal +int TPPLPartition::Triangulate_EC(TPPLPoly *poly, list *triangles) { + long numvertices; + PartitionVertex *vertices = NULL; + PartitionVertex *ear = NULL; + TPPLPoly triangle; + long i,j; + bool earfound; + + if(poly->GetNumPoints() < 3) return 0; + if(poly->GetNumPoints() == 3) { + triangles->push_back(*poly); + return 1; + } + + numvertices = poly->GetNumPoints(); + + vertices = new PartitionVertex[numvertices]; + for(i=0;iGetPoint(i); + if(i==(numvertices-1)) vertices[i].next=&(vertices[0]); + else vertices[i].next=&(vertices[i+1]); + if(i==0) vertices[i].previous = &(vertices[numvertices-1]); + else vertices[i].previous = &(vertices[i-1]); + } + for(i=0;i ear->angle) { + ear = &(vertices[j]); + } + } + } + if(!earfound) { + delete [] vertices; + return 0; + } + + triangle.Triangle(ear->previous->p,ear->p,ear->next->p); + triangles->push_back(triangle); + + ear->isActive = false; + ear->previous->next = ear->next; + ear->next->previous = ear->previous; + + if(i==numvertices-4) break; + + UpdateVertex(ear->previous,vertices,numvertices); + UpdateVertex(ear->next,vertices,numvertices); + } + for(i=0;ip,vertices[i].p,vertices[i].next->p); + triangles->push_back(triangle); + break; + } + } + + delete [] vertices; + + return 1; +} + +int TPPLPartition::Triangulate_EC(list *inpolys, list *triangles) { + list outpolys; + list::iterator iter; + + if(!RemoveHoles(inpolys,&outpolys)) return 0; + for(iter=outpolys.begin();iter!=outpolys.end();iter++) { + if(!Triangulate_EC(&(*iter),triangles)) return 0; + } + return 1; +} + +int TPPLPartition::ConvexPartition_HM(TPPLPoly *poly, list *parts) { + list triangles; + list::iterator iter1,iter2; + TPPLPoly *poly1 = NULL,*poly2 = NULL; + TPPLPoly newpoly; + TPPLPoint d1,d2,p1,p2,p3; + long i11,i12,i21,i22,i13,i23,j,k; + bool isdiagonal; + long numreflex; + + //check if the poly is already convex + numreflex = 0; + for(i11=0;i11GetNumPoints();i11++) { + if(i11==0) i12 = poly->GetNumPoints()-1; + else i12=i11-1; + if(i11==(poly->GetNumPoints()-1)) i13=0; + else i13=i11+1; + if(IsReflex(poly->GetPoint(i12),poly->GetPoint(i11),poly->GetPoint(i13))) { + numreflex = 1; + break; + } + } + if(numreflex == 0) { + parts->push_back(*poly); + return 1; + } + + if(!Triangulate_EC(poly,&triangles)) return 0; + + for(iter1 = triangles.begin(); iter1 != triangles.end(); iter1++) { + poly1 = &(*iter1); + for(i11=0;i11GetNumPoints();i11++) { + d1 = poly1->GetPoint(i11); + i12 = (i11+1)%(poly1->GetNumPoints()); + d2 = poly1->GetPoint(i12); + + isdiagonal = false; + for(iter2 = iter1; iter2 != triangles.end(); iter2++) { + if(iter1 == iter2) continue; + poly2 = &(*iter2); + + for(i21=0;i21GetNumPoints();i21++) { + if((d2.x != poly2->GetPoint(i21).x)||(d2.y != poly2->GetPoint(i21).y)) continue; + i22 = (i21+1)%(poly2->GetNumPoints()); + if((d1.x != poly2->GetPoint(i22).x)||(d1.y != poly2->GetPoint(i22).y)) continue; + isdiagonal = true; + break; + } + if(isdiagonal) break; + } + + if(!isdiagonal) continue; + + p2 = poly1->GetPoint(i11); + if(i11 == 0) i13 = poly1->GetNumPoints()-1; + else i13 = i11-1; + p1 = poly1->GetPoint(i13); + if(i22 == (poly2->GetNumPoints()-1)) i23 = 0; + else i23 = i22+1; + p3 = poly2->GetPoint(i23); + + if(!IsConvex(p1,p2,p3)) continue; + + p2 = poly1->GetPoint(i12); + if(i12 == (poly1->GetNumPoints()-1)) i13 = 0; + else i13 = i12+1; + p3 = poly1->GetPoint(i13); + if(i21 == 0) i23 = poly2->GetNumPoints()-1; + else i23 = i21-1; + p1 = poly2->GetPoint(i23); + + if(!IsConvex(p1,p2,p3)) continue; + + newpoly.Init(poly1->GetNumPoints()+poly2->GetNumPoints()-2); + k = 0; + for(j=i12;j!=i11;j=(j+1)%(poly1->GetNumPoints())) { + newpoly[k] = poly1->GetPoint(j); + k++; + } + for(j=i22;j!=i21;j=(j+1)%(poly2->GetNumPoints())) { + newpoly[k] = poly2->GetPoint(j); + k++; + } + + triangles.erase(iter2); + *iter1 = newpoly; + poly1 = &(*iter1); + i11 = -1; + + continue; + } + } + + for(iter1 = triangles.begin(); iter1 != triangles.end(); iter1++) { + parts->push_back(*iter1); + } + + return 1; +} + +int TPPLPartition::ConvexPartition_HM(list *inpolys, list *parts) { + list outpolys; + list::iterator iter; + + if(!RemoveHoles(inpolys,&outpolys)) return 0; + for(iter=outpolys.begin();iter!=outpolys.end();iter++) { + if(!ConvexPartition_HM(&(*iter),parts)) return 0; + } + return 1; +} + +//minimum-weight polygon triangulation by dynamic programming +//O(n^3) time complexity +//O(n^2) space complexity +int TPPLPartition::Triangulate_OPT(TPPLPoly *poly, list *triangles) { + long i,j,k,gap,n; + DPState **dpstates = NULL; + TPPLPoint p1,p2,p3,p4; + long bestvertex; + tppl_float weight,minweight,d1,d2; + Diagonal diagonal,newdiagonal; + list diagonals; + TPPLPoly triangle; + int ret = 1; + + n = poly->GetNumPoints(); + dpstates = new DPState *[n]; + for(i=1;iGetPoint(i); + for(j=i+1;jGetPoint(j); + + //visibility check + if(i==0) p3 = poly->GetPoint(n-1); + else p3 = poly->GetPoint(i-1); + if(i==(n-1)) p4 = poly->GetPoint(0); + else p4 = poly->GetPoint(i+1); + if(!InCone(p3,p1,p4,p2)) { + dpstates[j][i].visible = false; + continue; + } + + if(j==0) p3 = poly->GetPoint(n-1); + else p3 = poly->GetPoint(j-1); + if(j==(n-1)) p4 = poly->GetPoint(0); + else p4 = poly->GetPoint(j+1); + if(!InCone(p3,p2,p4,p1)) { + dpstates[j][i].visible = false; + continue; + } + + for(k=0;kGetPoint(k); + if(k==(n-1)) p4 = poly->GetPoint(0); + else p4 = poly->GetPoint(k+1); + if(Intersects(p1,p2,p3,p4)) { + dpstates[j][i].visible = false; + break; + } + } + } + } + } + dpstates[n-1][0].visible = true; + dpstates[n-1][0].weight = 0; + dpstates[n-1][0].bestvertex = -1; + + for(gap = 2; gapGetPoint(i),poly->GetPoint(k)); + if(j<=(k+1)) d2=0; + else d2 = Distance(poly->GetPoint(k),poly->GetPoint(j)); + + weight = dpstates[k][i].weight + dpstates[j][k].weight + d1 + d2; + + if((bestvertex == -1)||(weightGetPoint(diagonal.index1),poly->GetPoint(bestvertex),poly->GetPoint(diagonal.index2)); + triangles->push_back(triangle); + if(bestvertex > (diagonal.index1+1)) { + newdiagonal.index1 = diagonal.index1; + newdiagonal.index2 = bestvertex; + diagonals.push_back(newdiagonal); + } + if(diagonal.index2 > (bestvertex+1)) { + newdiagonal.index1 = bestvertex; + newdiagonal.index2 = diagonal.index2; + diagonals.push_back(newdiagonal); + } + } + + for(i=1;i *pairs = NULL; + long w2; + + w2 = dpstates[a][b].weight; + if(w>w2) return; + + pairs = &(dpstates[a][b].pairs); + newdiagonal.index1 = i; + newdiagonal.index2 = j; + + if(wclear(); + pairs->push_front(newdiagonal); + dpstates[a][b].weight = w; + } else { + if((!pairs->empty())&&(i <= pairs->begin()->index1)) return; + while((!pairs->empty())&&(pairs->begin()->index2 >= j)) pairs->pop_front(); + pairs->push_front(newdiagonal); + } +} + +void TPPLPartition::TypeA(long i, long j, long k, PartitionVertex *vertices, DPState2 **dpstates) { + list *pairs = NULL; + list::iterator iter,lastiter; + long top; + long w; + + if(!dpstates[i][j].visible) return; + top = j; + w = dpstates[i][j].weight; + if(k-j > 1) { + if (!dpstates[j][k].visible) return; + w += dpstates[j][k].weight + 1; + } + if(j-i > 1) { + pairs = &(dpstates[i][j].pairs); + iter = pairs->end(); + lastiter = pairs->end(); + while(iter!=pairs->begin()) { + iter--; + if(!IsReflex(vertices[iter->index2].p,vertices[j].p,vertices[k].p)) lastiter = iter; + else break; + } + if(lastiter == pairs->end()) w++; + else { + if(IsReflex(vertices[k].p,vertices[i].p,vertices[lastiter->index1].p)) w++; + else top = lastiter->index1; + } + } + UpdateState(i,k,w,top,j,dpstates); +} + +void TPPLPartition::TypeB(long i, long j, long k, PartitionVertex *vertices, DPState2 **dpstates) { + list *pairs = NULL; + list::iterator iter,lastiter; + long top; + long w; + + if(!dpstates[j][k].visible) return; + top = j; + w = dpstates[j][k].weight; + + if (j-i > 1) { + if (!dpstates[i][j].visible) return; + w += dpstates[i][j].weight + 1; + } + if (k-j > 1) { + pairs = &(dpstates[j][k].pairs); + + iter = pairs->begin(); + if((!pairs->empty())&&(!IsReflex(vertices[i].p,vertices[j].p,vertices[iter->index1].p))) { + lastiter = iter; + while(iter!=pairs->end()) { + if(!IsReflex(vertices[i].p,vertices[j].p,vertices[iter->index1].p)) { + lastiter = iter; + iter++; + } + else break; + } + if(IsReflex(vertices[lastiter->index2].p,vertices[k].p,vertices[i].p)) w++; + else top = lastiter->index2; + } else w++; + } + UpdateState(i,k,w,j,top,dpstates); +} + +int TPPLPartition::ConvexPartition_OPT(TPPLPoly *poly, list *parts) { + TPPLPoint p1,p2,p3,p4; + PartitionVertex *vertices = NULL; + DPState2 **dpstates = NULL; + long i,j,k,n,gap; + list diagonals,diagonals2; + Diagonal diagonal,newdiagonal; + list *pairs = NULL,*pairs2 = NULL; + list::iterator iter,iter2; + int ret; + TPPLPoly newpoly; + list indices; + list::iterator iiter; + bool ijreal,jkreal; + + n = poly->GetNumPoints(); + vertices = new PartitionVertex[n]; + + dpstates = new DPState2 *[n]; + for(i=0;iGetPoint(i); + vertices[i].isActive = true; + if(i==0) vertices[i].previous = &(vertices[n-1]); + else vertices[i].previous = &(vertices[i-1]); + if(i==(poly->GetNumPoints()-1)) vertices[i].next = &(vertices[0]); + else vertices[i].next = &(vertices[i+1]); + } + for(i=1;iGetPoint(i); + for(j=i+1;jGetPoint(j); + + //visibility check + if(!InCone(&vertices[i],p2)) { + dpstates[i][j].visible = false; + continue; + } + if(!InCone(&vertices[j],p1)) { + dpstates[i][j].visible = false; + continue; + } + + for(k=0;kGetPoint(k); + if(k==(n-1)) p4 = poly->GetPoint(0); + else p4 = poly->GetPoint(k+1); + if(Intersects(p1,p2,p3,p4)) { + dpstates[i][j].visible = false; + break; + } + } + } + } + } + for(i=0;i<(n-2);i++) { + j = i+2; + if(dpstates[i][j].visible) { + dpstates[i][j].weight = 0; + newdiagonal.index1 = i+1; + newdiagonal.index2 = i+1; + dpstates[i][j].pairs.push_back(newdiagonal); + } + } + + dpstates[0][n-1].visible = true; + vertices[0].isConvex = false; //by convention + + for(gap=3; gapempty()) { + ret = 0; + break; + } + if(!vertices[diagonal.index1].isConvex) { + iter = pairs->end(); + iter--; + j = iter->index2; + newdiagonal.index1 = j; + newdiagonal.index2 = diagonal.index2; + diagonals.push_front(newdiagonal); + if((j - diagonal.index1)>1) { + if(iter->index1 != iter->index2) { + pairs2 = &(dpstates[diagonal.index1][j].pairs); + while(1) { + if(pairs2->empty()) { + ret = 0; + break; + } + iter2 = pairs2->end(); + iter2--; + if(iter->index1 != iter2->index1) pairs2->pop_back(); + else break; + } + if(ret == 0) break; + } + newdiagonal.index1 = diagonal.index1; + newdiagonal.index2 = j; + diagonals.push_front(newdiagonal); + } + } else { + iter = pairs->begin(); + j = iter->index1; + newdiagonal.index1 = diagonal.index1; + newdiagonal.index2 = j; + diagonals.push_front(newdiagonal); + if((diagonal.index2 - j) > 1) { + if(iter->index1 != iter->index2) { + pairs2 = &(dpstates[j][diagonal.index2].pairs); + while(1) { + if(pairs2->empty()) { + ret = 0; + break; + } + iter2 = pairs2->begin(); + if(iter->index2 != iter2->index2) pairs2->pop_front(); + else break; + } + if(ret == 0) break; + } + newdiagonal.index1 = j; + newdiagonal.index2 = diagonal.index2; + diagonals.push_front(newdiagonal); + } + } + } + + if(ret == 0) { + for(i=0;iend(); + iter--; + j = iter->index2; + if(iter->index1 != iter->index2) ijreal = false; + } else { + iter = pairs->begin(); + j = iter->index1; + if(iter->index1 != iter->index2) jkreal = false; + } + + newdiagonal.index1 = diagonal.index1; + newdiagonal.index2 = j; + if(ijreal) { + diagonals.push_back(newdiagonal); + } else { + diagonals2.push_back(newdiagonal); + } + + newdiagonal.index1 = j; + newdiagonal.index2 = diagonal.index2; + if(jkreal) { + diagonals.push_back(newdiagonal); + } else { + diagonals2.push_back(newdiagonal); + } + + indices.push_back(j); + } + + indices.sort(); + newpoly.Init((long)indices.size()); + k=0; + for(iiter = indices.begin();iiter!=indices.end();iiter++) { + newpoly[k] = vertices[*iiter].p; + k++; + } + parts->push_back(newpoly); + } + + for(i=0;i *inpolys, list *monotonePolys) { + list::iterator iter; + MonotoneVertex *vertices = NULL; + long i,numvertices,vindex,vindex2,newnumvertices,maxnumvertices; + long polystartindex, polyendindex; + TPPLPoly *poly = NULL; + MonotoneVertex *v = NULL,*v2 = NULL,*vprev = NULL,*vnext = NULL; + ScanLineEdge newedge; + bool error = false; + + numvertices = 0; + for(iter = inpolys->begin(); iter != inpolys->end(); iter++) { + numvertices += iter->GetNumPoints(); + } + + maxnumvertices = numvertices*3; + vertices = new MonotoneVertex[maxnumvertices]; + newnumvertices = numvertices; + + polystartindex = 0; + for(iter = inpolys->begin(); iter != inpolys->end(); iter++) { + poly = &(*iter); + polyendindex = polystartindex + poly->GetNumPoints()-1; + for(i=0;iGetNumPoints();i++) { + vertices[i+polystartindex].p = poly->GetPoint(i); + if(i==0) vertices[i+polystartindex].previous = polyendindex; + else vertices[i+polystartindex].previous = i+polystartindex-1; + if(i==(poly->GetNumPoints()-1)) vertices[i+polystartindex].next = polystartindex; + else vertices[i+polystartindex].next = i+polystartindex+1; + } + polystartindex = polyendindex+1; + } + + //construct the priority queue + long *priority = new long [numvertices]; + for(i=0;iprevious]); + vnext = &(vertices[v->next]); + + if(Below(vprev->p,v->p)&&Below(vnext->p,v->p)) { + if(IsConvex(vnext->p,vprev->p,v->p)) { + vertextypes[i] = TPPL_VERTEXTYPE_START; + } else { + vertextypes[i] = TPPL_VERTEXTYPE_SPLIT; + } + } else if(Below(v->p,vprev->p)&&Below(v->p,vnext->p)) { + if(IsConvex(vnext->p,vprev->p,v->p)) + { + vertextypes[i] = TPPL_VERTEXTYPE_END; + } else { + vertextypes[i] = TPPL_VERTEXTYPE_MERGE; + } + } else { + vertextypes[i] = TPPL_VERTEXTYPE_REGULAR; + } + } + + //helpers + long *helpers = new long[maxnumvertices]; + + //binary search tree that holds edges intersecting the scanline + //note that while set doesn't actually have to be implemented as a tree + //complexity requirements for operations are the same as for the balanced binary search tree + set edgeTree; + //store iterators to the edge tree elements + //this makes deleting existing edges much faster + set::iterator *edgeTreeIterators,edgeIter; + edgeTreeIterators = new set::iterator[maxnumvertices]; + pair::iterator,bool> edgeTreeRet; + for(i = 0; ip; + newedge.p2 = vertices[v->next].p; + newedge.index = vindex; + edgeTreeRet = edgeTree.insert(newedge); + edgeTreeIterators[vindex] = edgeTreeRet.first; + helpers[vindex] = vindex; + break; + + case TPPL_VERTEXTYPE_END: + //if helper(ei-1) is a merge vertex + if(vertextypes[helpers[v->previous]]==TPPL_VERTEXTYPE_MERGE) { + //Insert the diagonal connecting vi to helper(ei-1) in D. + AddDiagonal(vertices,&newnumvertices,vindex,helpers[v->previous], + vertextypes, edgeTreeIterators, &edgeTree, helpers); + } + //Delete ei-1 from T + edgeTree.erase(edgeTreeIterators[v->previous]); + break; + + case TPPL_VERTEXTYPE_SPLIT: + //Search in T to find the edge e j directly left of vi. + newedge.p1 = v->p; + newedge.p2 = v->p; + edgeIter = edgeTree.lower_bound(newedge); + if(edgeIter == edgeTree.begin()) { + error = true; + break; + } + edgeIter--; + //Insert the diagonal connecting vi to helper(ej) in D. + AddDiagonal(vertices,&newnumvertices,vindex,helpers[edgeIter->index], + vertextypes, edgeTreeIterators, &edgeTree, helpers); + vindex2 = newnumvertices-2; + v2 = &(vertices[vindex2]); + //helper(e j)�vi + helpers[edgeIter->index] = vindex; + //Insert ei in T and set helper(ei) to vi. + newedge.p1 = v2->p; + newedge.p2 = vertices[v2->next].p; + newedge.index = vindex2; + edgeTreeRet = edgeTree.insert(newedge); + edgeTreeIterators[vindex2] = edgeTreeRet.first; + helpers[vindex2] = vindex2; + break; + + case TPPL_VERTEXTYPE_MERGE: + //if helper(ei-1) is a merge vertex + if(vertextypes[helpers[v->previous]]==TPPL_VERTEXTYPE_MERGE) { + //Insert the diagonal connecting vi to helper(ei-1) in D. + AddDiagonal(vertices,&newnumvertices,vindex,helpers[v->previous], + vertextypes, edgeTreeIterators, &edgeTree, helpers); + vindex2 = newnumvertices-2; + v2 = &(vertices[vindex2]); + } + //Delete ei-1 from T. + edgeTree.erase(edgeTreeIterators[v->previous]); + //Search in T to find the edge e j directly left of vi. + newedge.p1 = v->p; + newedge.p2 = v->p; + edgeIter = edgeTree.lower_bound(newedge); + if(edgeIter == edgeTree.begin()) { + error = true; + break; + } + edgeIter--; + //if helper(ej) is a merge vertex + if(vertextypes[helpers[edgeIter->index]]==TPPL_VERTEXTYPE_MERGE) { + //Insert the diagonal connecting vi to helper(e j) in D. + AddDiagonal(vertices,&newnumvertices,vindex2,helpers[edgeIter->index], + vertextypes, edgeTreeIterators, &edgeTree, helpers); + } + //helper(e j)�vi + helpers[edgeIter->index] = vindex2; + break; + + case TPPL_VERTEXTYPE_REGULAR: + //if the interior of P lies to the right of vi + if(Below(v->p,vertices[v->previous].p)) { + //if helper(ei-1) is a merge vertex + if(vertextypes[helpers[v->previous]]==TPPL_VERTEXTYPE_MERGE) { + //Insert the diagonal connecting vi to helper(ei-1) in D. + AddDiagonal(vertices,&newnumvertices,vindex,helpers[v->previous], + vertextypes, edgeTreeIterators, &edgeTree, helpers); + vindex2 = newnumvertices-2; + v2 = &(vertices[vindex2]); + } + //Delete ei-1 from T. + edgeTree.erase(edgeTreeIterators[v->previous]); + //Insert ei in T and set helper(ei) to vi. + newedge.p1 = v2->p; + newedge.p2 = vertices[v2->next].p; + newedge.index = vindex2; + edgeTreeRet = edgeTree.insert(newedge); + edgeTreeIterators[vindex2] = edgeTreeRet.first; + helpers[vindex2] = vindex; + } else { + //Search in T to find the edge ej directly left of vi. + newedge.p1 = v->p; + newedge.p2 = v->p; + edgeIter = edgeTree.lower_bound(newedge); + if(edgeIter == edgeTree.begin()) { + error = true; + break; + } + edgeIter--; + //if helper(ej) is a merge vertex + if(vertextypes[helpers[edgeIter->index]]==TPPL_VERTEXTYPE_MERGE) { + //Insert the diagonal connecting vi to helper(e j) in D. + AddDiagonal(vertices,&newnumvertices,vindex,helpers[edgeIter->index], + vertextypes, edgeTreeIterators, &edgeTree, helpers); + } + //helper(e j)�vi + helpers[edgeIter->index] = vindex; + } + break; + } + + if(error) break; + } + + char *used = new char[newnumvertices]; + memset(used,0,newnumvertices*sizeof(char)); + + if(!error) { + //return result + long size; + TPPLPoly mpoly; + for(i=0;inext]); + size = 1; + while(vnext!=v) { + vnext = &(vertices[vnext->next]); + size++; + } + mpoly.Init(size); + v = &(vertices[i]); + mpoly[0] = v->p; + vnext = &(vertices[v->next]); + size = 1; + used[i] = 1; + used[v->next] = 1; + while(vnext!=v) { + mpoly[size] = vnext->p; + used[vnext->next] = 1; + vnext = &(vertices[vnext->next]); + size++; + } + monotonePolys->push_back(mpoly); + } + } + + //cleanup + delete [] vertices; + delete [] priority; + delete [] vertextypes; + delete [] edgeTreeIterators; + delete [] helpers; + delete [] used; + + if(error) { + return 0; + } else { + return 1; + } +} + +//adds a diagonal to the doubly-connected list of vertices +void TPPLPartition::AddDiagonal(MonotoneVertex *vertices, long *numvertices, long index1, long index2, + char *vertextypes, set::iterator *edgeTreeIterators, + set *edgeTree, long *helpers) +{ + long newindex1,newindex2; + + newindex1 = *numvertices; + (*numvertices)++; + newindex2 = *numvertices; + (*numvertices)++; + + vertices[newindex1].p = vertices[index1].p; + vertices[newindex2].p = vertices[index2].p; + + vertices[newindex2].next = vertices[index2].next; + vertices[newindex1].next = vertices[index1].next; + + vertices[vertices[index2].next].previous = newindex2; + vertices[vertices[index1].next].previous = newindex1; + + vertices[index1].next = newindex2; + vertices[newindex2].previous = index1; + + vertices[index2].next = newindex1; + vertices[newindex1].previous = index2; + + //update all relevant structures + vertextypes[newindex1] = vertextypes[index1]; + edgeTreeIterators[newindex1] = edgeTreeIterators[index1]; + helpers[newindex1] = helpers[index1]; + if(edgeTreeIterators[newindex1] != edgeTree->end()) + edgeTreeIterators[newindex1]->index = newindex1; + vertextypes[newindex2] = vertextypes[index2]; + edgeTreeIterators[newindex2] = edgeTreeIterators[index2]; + helpers[newindex2] = helpers[index2]; + if(edgeTreeIterators[newindex2] != edgeTree->end()) + edgeTreeIterators[newindex2]->index = newindex2; +} + +bool TPPLPartition::Below(TPPLPoint &p1, TPPLPoint &p2) { + if(p1.y < p2.y) return true; + else if(p1.y == p2.y) { + if(p1.x < p2.x) return true; + } + return false; +} + +//sorts in the falling order of y values, if y is equal, x is used instead +bool TPPLPartition::VertexSorter::operator() (long index1, long index2) { + if(vertices[index1].p.y > vertices[index2].p.y) return true; + else if(vertices[index1].p.y == vertices[index2].p.y) { + if(vertices[index1].p.x > vertices[index2].p.x) return true; + } + return false; +} + +bool TPPLPartition::ScanLineEdge::IsConvex(const TPPLPoint& p1, const TPPLPoint& p2, const TPPLPoint& p3) const { + tppl_float tmp; + tmp = (p3.y-p1.y)*(p2.x-p1.x)-(p3.x-p1.x)*(p2.y-p1.y); + if(tmp>0) return 1; + else return 0; +} + +bool TPPLPartition::ScanLineEdge::operator < (const ScanLineEdge & other) const { + if(other.p1.y == other.p2.y) { + if(p1.y == p2.y) { + if(p1.y < other.p1.y) return true; + else return false; + } + if(IsConvex(p1,p2,other.p1)) return true; + else return false; + } else if(p1.y == p2.y) { + if(IsConvex(other.p1,other.p2,p1)) return false; + else return true; + } else if(p1.y < other.p1.y) { + if(IsConvex(other.p1,other.p2,p1)) return false; + else return true; + } else { + if(IsConvex(p1,p2,other.p1)) return true; + else return false; + } +} + +//triangulates monotone polygon +//O(n) time, O(n) space complexity +int TPPLPartition::TriangulateMonotone(TPPLPoly *inPoly, list *triangles) { + long i,i2,j,topindex,bottomindex,leftindex,rightindex,vindex; + TPPLPoint *points = NULL; + long numpoints; + TPPLPoly triangle; + + numpoints = inPoly->GetNumPoints(); + points = inPoly->GetPoints(); + + //trivial calses + if(numpoints < 3) return 0; + if(numpoints == 3) { + triangles->push_back(*inPoly); + return 1; + } + + topindex = 0; bottomindex=0; + for(i=1;i=numpoints) i2 = 0; + if(!Below(points[i2],points[i])) return 0; + i = i2; + } + i = bottomindex; + while(i!=topindex) { + i2 = i+1; if(i2>=numpoints) i2 = 0; + if(!Below(points[i],points[i2])) return 0; + i = i2; + } + + char *vertextypes = new char[numpoints]; + long *priority = new long[numpoints]; + + //merge left and right vertex chains + priority[0] = topindex; + vertextypes[topindex] = 0; + leftindex = topindex+1; if(leftindex>=numpoints) leftindex = 0; + rightindex = topindex-1; if(rightindex<0) rightindex = numpoints-1; + for(i=1;i<(numpoints-1);i++) { + if(leftindex==bottomindex) { + priority[i] = rightindex; + rightindex--; if(rightindex<0) rightindex = numpoints-1; + vertextypes[priority[i]] = -1; + } else if(rightindex==bottomindex) { + priority[i] = leftindex; + leftindex++; if(leftindex>=numpoints) leftindex = 0; + vertextypes[priority[i]] = 1; + } else { + if(Below(points[leftindex],points[rightindex])) { + priority[i] = rightindex; + rightindex--; if(rightindex<0) rightindex = numpoints-1; + vertextypes[priority[i]] = -1; + } else { + priority[i] = leftindex; + leftindex++; if(leftindex>=numpoints) leftindex = 0; + vertextypes[priority[i]] = 1; + } + } + } + priority[i] = bottomindex; + vertextypes[bottomindex] = 0; + + long *stack = new long[numpoints]; + long stackptr = 0; + + stack[0] = priority[0]; + stack[1] = priority[1]; + stackptr = 2; + + //for each vertex from top to bottom trim as many triangles as possible + for(i=2;i<(numpoints-1);i++) { + vindex = priority[i]; + if(vertextypes[vindex]!=vertextypes[stack[stackptr-1]]) { + for(j=0;j<(stackptr-1);j++) { + if(vertextypes[vindex]==1) { + triangle.Triangle(points[stack[j+1]],points[stack[j]],points[vindex]); + } else { + triangle.Triangle(points[stack[j]],points[stack[j+1]],points[vindex]); + } + triangles->push_back(triangle); + } + stack[0] = priority[i-1]; + stack[1] = priority[i]; + stackptr = 2; + } else { + stackptr--; + while(stackptr>0) { + if(vertextypes[vindex]==1) { + if(IsConvex(points[vindex],points[stack[stackptr-1]],points[stack[stackptr]])) { + triangle.Triangle(points[vindex],points[stack[stackptr-1]],points[stack[stackptr]]); + triangles->push_back(triangle); + stackptr--; + } else { + break; + } + } else { + if(IsConvex(points[vindex],points[stack[stackptr]],points[stack[stackptr-1]])) { + triangle.Triangle(points[vindex],points[stack[stackptr]],points[stack[stackptr-1]]); + triangles->push_back(triangle); + stackptr--; + } else { + break; + } + } + } + stackptr++; + stack[stackptr] = vindex; + stackptr++; + } + } + vindex = priority[i]; + for(j=0;j<(stackptr-1);j++) { + if(vertextypes[stack[j+1]]==1) { + triangle.Triangle(points[stack[j]],points[stack[j+1]],points[vindex]); + } else { + triangle.Triangle(points[stack[j+1]],points[stack[j]],points[vindex]); + } + triangles->push_back(triangle); + } + + delete [] priority; + delete [] vertextypes; + delete [] stack; + + return 1; +} + +int TPPLPartition::Triangulate_MONO(list *inpolys, list *triangles) { + list monotone; + list::iterator iter; + + if(!MonotonePartition(inpolys,&monotone)) return 0; + for(iter = monotone.begin(); iter!=monotone.end();iter++) { + if(!TriangulateMonotone(&(*iter),triangles)) return 0; + } + return 1; +} + +int TPPLPartition::Triangulate_MONO(TPPLPoly *poly, list *triangles) { + list polys; + polys.push_back(*poly); + + return Triangulate_MONO(&polys, triangles); +} diff --git a/canvas/polypartition/polypartition.h b/canvas/polypartition/polypartition.h new file mode 100644 index 000000000..60d927dd7 --- /dev/null +++ b/canvas/polypartition/polypartition.h @@ -0,0 +1,357 @@ +//Copyright (C) 2011 by Ivan Fratric +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in +//all copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +//THE SOFTWARE. + +#ifndef POLYPARTITION_H +#define POLYPARTITION_H + +#include +#include + +typedef double tppl_float; + +#define TPPL_CCW 1 +#define TPPL_CW -1 + +//2D point structure +struct TPPLPoint { + tppl_float x; + tppl_float y; + // User-specified vertex identifier. Note that this isn't used internally + // by the library, but will be faithfully copied around. + int id; + + TPPLPoint operator + (const TPPLPoint& p) const { + TPPLPoint r; + r.x = x + p.x; + r.y = y + p.y; + return r; + } + + TPPLPoint operator - (const TPPLPoint& p) const { + TPPLPoint r; + r.x = x - p.x; + r.y = y - p.y; + return r; + } + + TPPLPoint operator * (const tppl_float f ) const { + TPPLPoint r; + r.x = x*f; + r.y = y*f; + return r; + } + + TPPLPoint operator / (const tppl_float f ) const { + TPPLPoint r; + r.x = x/f; + r.y = y/f; + return r; + } + + bool operator==(const TPPLPoint& p) const { + if((x == p.x)&&(y==p.y)) return true; + else return false; + } + + bool operator!=(const TPPLPoint& p) const { + if((x == p.x)&&(y==p.y)) return false; + else return true; + } +}; + + +//Polygon implemented as an array of points with a 'hole' flag +class TPPLPoly { + protected: + + TPPLPoint *points; + long numpoints; + bool hole; + + public: + + //constructors/destructors + TPPLPoly(); + ~TPPLPoly(); + + TPPLPoly(const TPPLPoly &src); + TPPLPoly& operator=(const TPPLPoly &src); + + //getters and setters + long GetNumPoints() { + return numpoints; + } + + bool IsHole() { + return hole; + } + + void SetHole(bool hole) { + this->hole = hole; + } + + TPPLPoint &GetPoint(long i) { + return points[i]; + } + + TPPLPoint *GetPoints() { + return points; + } + + TPPLPoint& operator[] (int i) { + return points[i]; + } + + //clears the polygon points + void Clear(); + + //inits the polygon with numpoints vertices + void Init(long numpoints); + + //creates a triangle with points p1,p2,p3 + void Triangle(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3); + + //inverts the orfer of vertices + void Invert(); + + //returns the orientation of the polygon + //possible values: + // TPPL_CCW : polygon vertices are in counter-clockwise order + // TPPL_CW : polygon vertices are in clockwise order + // 0 : the polygon has no (measurable) area + int GetOrientation(); + + //sets the polygon orientation + //orientation can be + // TPPL_CCW : sets vertices in counter-clockwise order + // TPPL_CW : sets vertices in clockwise order + void SetOrientation(int orientation); +}; + + +class TPPLPartition { + protected: + struct PartitionVertex { + bool isActive; + bool isConvex; + bool isEar; + + TPPLPoint p; + tppl_float angle; + PartitionVertex *previous; + PartitionVertex *next; + + PartitionVertex(); + }; + + struct MonotoneVertex { + TPPLPoint p; + long previous; + long next; + }; + + class VertexSorter{ + MonotoneVertex *vertices; + public: + VertexSorter(MonotoneVertex *v) : vertices(v) {} + bool operator() (long index1, long index2); + }; + + struct Diagonal { + long index1; + long index2; + }; + + //dynamic programming state for minimum-weight triangulation + struct DPState { + bool visible; + tppl_float weight; + long bestvertex; + }; + + //dynamic programming state for convex partitioning + struct DPState2 { + bool visible; + long weight; + std::list pairs; + }; + + //edge that intersects the scanline + struct ScanLineEdge { + mutable long index; + TPPLPoint p1; + TPPLPoint p2; + + //determines if the edge is to the left of another edge + bool operator< (const ScanLineEdge & other) const; + + bool IsConvex(const TPPLPoint& p1, const TPPLPoint& p2, const TPPLPoint& p3) const; + }; + + //standard helper functions + bool IsConvex(TPPLPoint& p1, TPPLPoint& p2, TPPLPoint& p3); + bool IsReflex(TPPLPoint& p1, TPPLPoint& p2, TPPLPoint& p3); + bool IsInside(TPPLPoint& p1, TPPLPoint& p2, TPPLPoint& p3, TPPLPoint &p); + + bool InCone(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3, TPPLPoint &p); + bool InCone(PartitionVertex *v, TPPLPoint &p); + + int Intersects(TPPLPoint &p11, TPPLPoint &p12, TPPLPoint &p21, TPPLPoint &p22); + + TPPLPoint Normalize(const TPPLPoint &p); + tppl_float Distance(const TPPLPoint &p1, const TPPLPoint &p2); + + //helper functions for Triangulate_EC + void UpdateVertexReflexity(PartitionVertex *v); + void UpdateVertex(PartitionVertex *v,PartitionVertex *vertices, long numvertices); + + //helper functions for ConvexPartition_OPT + void UpdateState(long a, long b, long w, long i, long j, DPState2 **dpstates); + void TypeA(long i, long j, long k, PartitionVertex *vertices, DPState2 **dpstates); + void TypeB(long i, long j, long k, PartitionVertex *vertices, DPState2 **dpstates); + + //helper functions for MonotonePartition + bool Below(TPPLPoint &p1, TPPLPoint &p2); + void AddDiagonal(MonotoneVertex *vertices, long *numvertices, long index1, long index2, + char *vertextypes, std::set::iterator *edgeTreeIterators, + std::set *edgeTree, long *helpers); + + //triangulates a monotone polygon, used in Triangulate_MONO + int TriangulateMonotone(TPPLPoly *inPoly, std::list *triangles); + + public: + + //simple heuristic procedure for removing holes from a list of polygons + //works by creating a diagonal from the rightmost hole vertex to some visible vertex + //time complexity: O(h*(n^2)), h is the number of holes, n is the number of vertices + //space complexity: O(n) + //params: + // inpolys : a list of polygons that can contain holes + // vertices of all non-hole polys have to be in counter-clockwise order + // vertices of all hole polys have to be in clockwise order + // outpolys : a list of polygons without holes + //returns 1 on success, 0 on failure + int RemoveHoles(std::list *inpolys, std::list *outpolys); + + //triangulates a polygon by ear clipping + //time complexity O(n^2), n is the number of vertices + //space complexity: O(n) + //params: + // poly : an input polygon to be triangulated + // vertices have to be in counter-clockwise order + // triangles : a list of triangles (result) + //returns 1 on success, 0 on failure + int Triangulate_EC(TPPLPoly *poly, std::list *triangles); + + //triangulates a list of polygons that may contain holes by ear clipping algorithm + //first calls RemoveHoles to get rid of the holes, and then Triangulate_EC for each resulting polygon + //time complexity: O(h*(n^2)), h is the number of holes, n is the number of vertices + //space complexity: O(n) + //params: + // inpolys : a list of polygons to be triangulated (can contain holes) + // vertices of all non-hole polys have to be in counter-clockwise order + // vertices of all hole polys have to be in clockwise order + // triangles : a list of triangles (result) + //returns 1 on success, 0 on failure + int Triangulate_EC(std::list *inpolys, std::list *triangles); + + //creates an optimal polygon triangulation in terms of minimal edge length + //time complexity: O(n^3), n is the number of vertices + //space complexity: O(n^2) + //params: + // poly : an input polygon to be triangulated + // vertices have to be in counter-clockwise order + // triangles : a list of triangles (result) + //returns 1 on success, 0 on failure + int Triangulate_OPT(TPPLPoly *poly, std::list *triangles); + + //triangulates a polygons by firstly partitioning it into monotone polygons + //time complexity: O(n*log(n)), n is the number of vertices + //space complexity: O(n) + //params: + // poly : an input polygon to be triangulated + // vertices have to be in counter-clockwise order + // triangles : a list of triangles (result) + //returns 1 on success, 0 on failure + int Triangulate_MONO(TPPLPoly *poly, std::list *triangles); + + //triangulates a list of polygons by firstly partitioning them into monotone polygons + //time complexity: O(n*log(n)), n is the number of vertices + //space complexity: O(n) + //params: + // inpolys : a list of polygons to be triangulated (can contain holes) + // vertices of all non-hole polys have to be in counter-clockwise order + // vertices of all hole polys have to be in clockwise order + // triangles : a list of triangles (result) + //returns 1 on success, 0 on failure + int Triangulate_MONO(std::list *inpolys, std::list *triangles); + + //creates a monotone partition of a list of polygons that can contain holes + //time complexity: O(n*log(n)), n is the number of vertices + //space complexity: O(n) + //params: + // inpolys : a list of polygons to be triangulated (can contain holes) + // vertices of all non-hole polys have to be in counter-clockwise order + // vertices of all hole polys have to be in clockwise order + // monotonePolys : a list of monotone polygons (result) + //returns 1 on success, 0 on failure + int MonotonePartition(std::list *inpolys, std::list *monotonePolys); + + //partitions a polygon into convex polygons by using Hertel-Mehlhorn algorithm + //the algorithm gives at most four times the number of parts as the optimal algorithm + //however, in practice it works much better than that and often gives optimal partition + //uses triangulation obtained by ear clipping as intermediate result + //time complexity O(n^2), n is the number of vertices + //space complexity: O(n) + //params: + // poly : an input polygon to be partitioned + // vertices have to be in counter-clockwise order + // parts : resulting list of convex polygons + //returns 1 on success, 0 on failure + int ConvexPartition_HM(TPPLPoly *poly, std::list *parts); + + //partitions a list of polygons into convex parts by using Hertel-Mehlhorn algorithm + //the algorithm gives at most four times the number of parts as the optimal algorithm + //however, in practice it works much better than that and often gives optimal partition + //uses triangulation obtained by ear clipping as intermediate result + //time complexity O(n^2), n is the number of vertices + //space complexity: O(n) + //params: + // inpolys : an input list of polygons to be partitioned + // vertices of all non-hole polys have to be in counter-clockwise order + // vertices of all hole polys have to be in clockwise order + // parts : resulting list of convex polygons + //returns 1 on success, 0 on failure + int ConvexPartition_HM(std::list *inpolys, std::list *parts); + + //optimal convex partitioning (in terms of number of resulting convex polygons) + //using the Keil-Snoeyink algorithm + //M. Keil, J. Snoeyink, "On the time bound for convex decomposition of simple polygons", 1998 + //time complexity O(n^3), n is the number of vertices + //space complexity: O(n^3) + // poly : an input polygon to be partitioned + // vertices have to be in counter-clockwise order + // parts : resulting list of convex polygons + //returns 1 on success, 0 on failure + int ConvexPartition_OPT(TPPLPoly *poly, std::list *parts); +}; + + +#endif diff --git a/canvas/render.cpp b/canvas/render.cpp new file mode 100644 index 000000000..6fe4373c8 --- /dev/null +++ b/canvas/render.cpp @@ -0,0 +1,911 @@ +#include "canvas.hpp" +#include "polypartition/polypartition.h" +#include +#include +#include +#include "box_selection.hpp" +#include "selectables.hpp" +#include "symbol.hpp" +#include "sheet.hpp" +#include "frame.hpp" +#include "placement.hpp" +#include "text.hpp" +#include "line_net.hpp" +#include "net_label.hpp" +#include "bus_label.hpp" +#include "power_symbol.hpp" +#include "bus_ripper.hpp" +#include "target.hpp" +#include "triangle.hpp" +#include "padstack.hpp" +#include "polygon.hpp" +#include "hole.hpp" +#include "package.hpp" +#include "pad.hpp" +#include "core/core.hpp" +#include "layer_display.hpp" +#include "selection_filter.hpp" +#include "core/buffer.hpp" +#include "board.hpp" +#include "track.hpp" +#include "core/core_board.hpp" + +namespace horizon { + + void Canvas::render(const Junction &junc, bool interactive) { + Color c(1,1,0); + if(junc.net) { + c = Color(0,1,0); + } + if(junc.bus) { + c = Color::new_from_int(0xff, 0x66, 00); + } + if(junc.warning) { + draw_error(junc.position, 2e5, ""); + } + bool draw = true; + + + if(dynamic_cast(core) && junc.connection_count >= 2) + draw = false; + + if(draw) { + if(junc.connection_count == 2) { + draw_plus(junc.get_position(), 250000, c); + } + else if(junc.connection_count >= 3 && core->has_object_type(ObjectType::SCHEMATIC_SYMBOL)) { + draw_line(junc.position, junc.position+Coordi(0, 10), c, true, 0.5_mm); + } + else { + draw_cross(junc.position, 0.25_mm, c); + } + } + + if(interactive) { + selectables.append(junc.uuid, ObjectType::JUNCTION, junc.position); + if(!junc.temp) { + targets.emplace(junc.uuid, ObjectType::JUNCTION, transform.transform(junc.position)); + } + } + } + void Canvas::render(const PowerSymbol &sym) { + Color c(1,1,0); + /*if(sym.connection_count == 2) { + draw_plus(sym.junction->position, 0.25_mm, c); + } + else if(sym.connection_count >= 3 ) { + draw_arc(sym.junction->position, 0.25_mm, 0, 2*M_PI, c); + } + else { + draw_cross(sym.position, 0.25_mm, c); + }*/ + transform.shift = sym.junction->position; + draw_line({0,0}, {0, -1.25_mm}, c); + draw_line({-1.25_mm, -1.25_mm}, {1.25_mm, -1.25_mm}, c); + draw_line({-1.25_mm, -1.25_mm}, {0, -2.5_mm}, c); + draw_line({1.25_mm, -1.25_mm}, {0, -2.5_mm}, c); + selectables.append(sym.uuid, ObjectType::POWER_SYMBOL, {0,0}, {-1.25_mm, -2.5_mm}, {1.25_mm, 0_mm}, Selectable::Enlarge::FORCE); + transform.reset(); + + auto text_orientation = Orientation::RIGHT; + Coordi text_offset(1.25_mm, -1.875_mm); + if(sym.mirror) { + text_offset.x *= -1; + text_orientation = Orientation::LEFT; + } + + draw_text(sym.junction->position+text_offset, 1.25_mm, sym.junction->net->name, text_orientation, TextPlacement::CENTER, c); + } + + uint8_t Canvas::get_triangle_flags_for_line(int layer) { + bool hatch = (layer_display.count(layer) && layer_display.at(layer).mode == LayerDisplay::Mode::HATCH); + bool outline_mode = (layer_display.count(layer) && layer_display.at(layer).mode == LayerDisplay::Mode::OUTLINE); + return hatch | (outline_mode<<1); + } + + void Canvas::render(const Line &line, bool interactive) { + Color c(1,1,0); + c = core->get_layers().at(line.layer).color; + if(layer_display.count(line.layer)) { + c = layer_display.at(line.layer).color; + } + img_line(line.from->get_position(), line.to->get_position(), line.width, line.layer); + if(img_mode) + return; + auto flags = get_triangle_flags_for_line(line.layer); + if(line.width == 0) + flags = 0; + draw_line(line.from->get_position(), line.to->get_position(), c, true, line.width, flags); + if(interactive) + selectables.append(line.uuid, ObjectType::LINE, (line.from->get_position()+line.to->get_position())/2, Coordf::min(line.from->get_position(), line.to->get_position()), Coordf::max(line.from->get_position(), line.to->get_position()), Selectable::Enlarge::AUTO, 0, line.layer); + } + + void Canvas::render(const LineNet &line) { + uint64_t width = 0; + Color c(0,1,0); + if(line.net == nullptr) { + c = Color(1,0,0); + } + if(line.bus) { + c = Color::new_from_int(0xff, 0x66, 0); + width = 0.2_mm; + } + draw_line(line.from.get_position(), line.to.get_position(), c, true, width); + selectables.append(line.uuid, ObjectType::LINE_NET, (line.from.get_position()+line.to.get_position())/2, Coordf::min(line.from.get_position(), line.to.get_position()), Coordf::max(line.from.get_position(), line.to.get_position()), Selectable::Enlarge::AUTO); + } + + void Canvas::render(const Track &track) { + Color c(1,1,0); + c = core->get_layers().at(track.layer).color; + if(layer_display.count(track.layer)) { + c = layer_display.at(track.layer).color; + } + if(track.net == nullptr) { + c = Color::new_from_int(0xff, 0x66, 0); + } + if(track.is_air) { + c = {0,1,1}; + } + auto width = track.width; + if(track.width_from_net_class) { + if(track.net) { + width = track.net->net_class->default_width; + } + else { + width = 0; + } + } + if(!track.is_air && track.net) { + img_net(track.net); + img_line(track.from.get_position(), track.to.get_position(), width, track.layer); + img_net(nullptr); + } + if(img_mode) + return; + auto flags = get_triangle_flags_for_line(track.layer); + if(width == 0) + flags = 0; + draw_line(track.from.get_position(), track.to.get_position(), c, true, width, flags); + if(!track.is_air) + selectables.append(track.uuid, ObjectType::TRACK, (track.from.get_position()+track.to.get_position())/2, Coordf::min(track.from.get_position(), track.to.get_position()), Coordf::max(track.from.get_position(), track.to.get_position()), Selectable::Enlarge::AUTO, 0, track.layer); + } + + static const std::map omap_90 = { + {Orientation::LEFT, Orientation::DOWN}, + {Orientation::UP, Orientation::LEFT}, + {Orientation::RIGHT, Orientation::UP}, + {Orientation::DOWN, Orientation::RIGHT}, + }; + static const std::map omap_180 = { + {Orientation::LEFT, Orientation::RIGHT}, + {Orientation::UP, Orientation::DOWN}, + {Orientation::RIGHT, Orientation::LEFT}, + {Orientation::DOWN, Orientation::UP}, + }; + static const std::map omap_270 = { + {Orientation::LEFT, Orientation::UP}, + {Orientation::UP, Orientation::RIGHT}, + {Orientation::RIGHT, Orientation::DOWN}, + {Orientation::DOWN, Orientation::LEFT}, + }; + static const std::map omap_mirror = { + {Orientation::LEFT, Orientation::RIGHT}, + {Orientation::UP, Orientation::UP}, + {Orientation::RIGHT, Orientation::LEFT}, + {Orientation::DOWN, Orientation::DOWN}, + }; + + void Canvas::render(const SymbolPin &pin, bool interactive) { + Coordi p0 = transform.transform(pin.position); + Coordi p1 = p0; + + Coordi p_name = p0; + Coordi p_pad = p0; + + Orientation pin_orientation = pin.orientation; + if(transform.angle == 16384) { + pin_orientation = omap_90.at(pin_orientation); + } + if(transform.angle == 32768) { + pin_orientation = omap_180.at(pin_orientation); + } + if(transform.angle == 49152) { + pin_orientation = omap_270.at(pin_orientation); + } + if(transform.mirror) { + pin_orientation = omap_mirror.at(pin_orientation); + } + + Orientation name_orientation; + Orientation pad_orientation; + int64_t text_shift = 0.1_mm; + switch(pin_orientation) { + case Orientation::LEFT : + p1.x += pin.length; + p_name.x += pin.length+text_shift; + p_pad.x += pin.length-text_shift; + p_pad.y += text_shift; + name_orientation = Orientation::RIGHT; + pad_orientation = Orientation::LEFT; + break; + + case Orientation::RIGHT : + p1.x -= pin.length; + p_name.x -= pin.length+text_shift; + p_pad.x -= pin.length-text_shift; + p_pad.y += text_shift; + name_orientation = Orientation::LEFT; + pad_orientation = Orientation::RIGHT; + break; + + case Orientation::UP : + p1.y -= pin.length; + p_name.y -= pin.length+text_shift; + p_pad.y -= pin.length-text_shift; + p_pad.x -= text_shift; + name_orientation = Orientation::DOWN; + pad_orientation = Orientation::UP; + break; + + case Orientation::DOWN : + p1.y += pin.length; + p_name.y += pin.length+text_shift; + p_pad.y += pin.length-text_shift; + p_pad.x -= text_shift; + name_orientation = Orientation::UP; + pad_orientation = Orientation::DOWN; + break; + } + Color c(1,1,0); + Color c_name(1,1,1); + Color c_pad(1,1,1); + if(!pin.name_visible) { + c_name = {0.5, 0.5, 0.5}; + } + if(!pin.pad_visible) { + c_pad = {0.5, 0.5, 0.5}; + } + if(interactive || pin.name_visible) { + draw_text(p_name, 1.25_mm, pin.name, name_orientation, TextPlacement::CENTER, c_name, false); + } + std::pair pad_extents; + if(interactive || pin.pad_visible) { + pad_extents = draw_text(p_pad, 0.75_mm, pin.pad, pad_orientation, TextPlacement::BASELINE, c_pad, false); + } + switch(pin.connector_style) { + case SymbolPin::ConnectorStyle::BOX : + draw_box(p0, 0.25_mm, c, false); + break; + + case SymbolPin::ConnectorStyle::NONE : + break; + } + if(pin.connected_net_lines.size()>1) { + draw_line(p0, p0+Coordi(0, 10), c, false, 0.5_mm); + } + draw_line(p0, p1, c, false); + if(interactive) + selectables.append(pin.uuid, ObjectType::SYMBOL_PIN, p0, Coordf::min(pad_extents.first, Coordf::min(p0, p1)), Coordf::max(pad_extents.second, Coordf::max(p0, p1)), Selectable::Enlarge::FORCE); + } + + static int64_t sq(int64_t x) { + return x*x; + } + + void Canvas::render(const Arc &arc, bool interactive) { + Color co(1,1,0); + co = core->get_layers().at(arc.layer).color; + if(layer_display.count(arc.layer)) { + co = layer_display.at(arc.layer).color; + } + Coordf a(arc.from->get_position());// ,b,c; + Coordf b(arc.to->get_position());// ,b,c; + Coordf c(arc.center->get_position());// ,b,c; + if((sq(c.x-a.x) + sq(c.y-a.y)) != (sq(c.x-b.x) + sq(c.y-b.y))) { + Color ce(1,0,0); + draw_line(c, a, ce); + draw_line(c, b, ce); + draw_error(c, 2e5, "Arc center"); + return; + } + float radius = sqrt(sq(c.x-a.x) + sq(c.y-a.y)); + float a0 = atan2f(a.y-c.y, a.x-c.x); + float a1 = atan2f(b.y-c.y, b.x-c.x); + draw_arc(c, radius, a0, a1, co, true, arc.width); + Coordf t(radius, radius); + if(interactive) + selectables.append(arc.uuid, ObjectType::ARC, c, c-t, c+t, Selectable::Enlarge::AUTO, 0, arc.layer); + + } + + void Canvas::render(const SchematicSymbol &sym) { + //draw_error(sym.placement.shift, 2e5, "sch sym"); + transform = sym.placement; + render(sym.symbol, true, sym.smashed); + for(const auto &it: sym.symbol.pins) { + targets.emplace(UUIDPath<2>(sym.uuid, it.second.uuid), ObjectType::SYMBOL_PIN, transform.transform(it.second.position)); + } + auto bb = sym.symbol.get_bbox(); + selectables.append(sym.uuid, ObjectType::SCHEMATIC_SYMBOL, {0,0}, bb.first, bb.second, Selectable::Enlarge::FORCE); + transform.reset(); + } + + void Canvas::render(const Text &text, bool interactive, bool reorient) { + Color c(1,1,0); + c = core->get_layers().at(text.layer).color; + if(layer_display.count(text.layer)) { + c = layer_display.at(text.layer).color; + } + + + Coordi p0 = transform.transform(text.position); + Orientation text_orientation = text.orientation; + bool rev = core->get_layers().at(text.layer).reverse; + int angle = 0; + if(!rev) { + if((transform.angle == 16384)) { + text_orientation = omap_90.at(text_orientation); + } + else if(transform.angle == 32768) { + text_orientation = omap_180.at(text_orientation); + } + else if(transform.angle == 49152) { + text_orientation = omap_270.at(text_orientation); + } + else { + angle = transform.angle; + } + } + else { + if((transform.angle == 16384)) { + text_orientation = omap_270.at(text_orientation); + } + else if(transform.angle == 32768) { + text_orientation = omap_180.at(text_orientation); + } + else if(transform.angle == 49152) { + text_orientation = omap_90.at(text_orientation); + } + else { + angle = -transform.angle; + } + } + + + if(transform.mirror) { + text_orientation = omap_mirror.at(text_orientation); + angle = -angle; + } + + transform_save(); + transform.reset(); + transform.shift = p0; + transform.mirror = rev; + transform.angle = angle; + + img_text_layer(text.layer); + auto extents = draw_text({0,0}, text.size, text.overridden?text.text_override:text.text, text_orientation, text.placement, c, true, text.width); + img_text_layer(10000); + if(img_mode) { + transform_restore(); + return; + } + + if(interactive) + selectables.append(text.uuid, ObjectType::TEXT, {0,0}, extents.first, extents.second, Selectable::Enlarge::FORCE, 0, text.layer); + transform_restore(); + + } + + template + static std::string join(const T& v, const std::string& delim) { + std::ostringstream s; + for (const auto& i : v) { + if (i != *v.begin()) { + s << delim; + } + s << i; + } + return s.str(); + } + + void Canvas::render(const NetLabel &label) { + std::string txt = ""; + if(label.junction->net) { + txt = label.junction->net->name; + } + if(txt == "") { + txt = "? plz fix"; + } + if(label.on_sheets.size() > 0 && label.offsheet_refs){ + txt += " ["+join(label.on_sheets, ",")+"]"; + } + + if(label.style==NetLabel::Style::FLAG) { + Color c{0,1,0}; + std::pair extents; + Coordi shift; + std::tie(extents.first, extents.second, shift)= draw_flag(label.junction->position, txt, label.size, label.orientation, c); + selectables.append(label.uuid, ObjectType::NET_LABEL, label.junction->position+shift, extents.first, extents.second, Selectable::Enlarge::FORCE); + } + else { + auto extents = draw_text(label.junction->position, label.size, txt, label.orientation, TextPlacement::BASELINE, {0,1,0}, false); + selectables.append(label.uuid, ObjectType::NET_LABEL, label.junction->position+Coordi(0, 1000000), extents.first, extents.second, Selectable::Enlarge::FORCE); + } + } + void Canvas::render(const BusLabel &label) { + std::string txt = ""; + if(label.junction->bus) { + txt = "B:"+label.junction->bus->name; + } + if(label.on_sheets.size() > 0 && label.offsheet_refs){ + txt += " ["+join(label.on_sheets, ",")+"]"; + } + + auto c = Color::new_from_int(0xff, 0x66, 00); + std::pair extents; + Coordi shift; + std::tie(extents.first, extents.second, shift)= draw_flag(label.junction->position, txt, label.size, label.orientation, c); + selectables.append(label.uuid, ObjectType::BUS_LABEL, label.junction->position+shift, extents.first, extents.second, Selectable::Enlarge::FORCE); + } + + void Canvas::render(const BusRipper &ripper) { + auto c = Color::new_from_int(0xff, 0xb1, 00); + auto connector_pos = ripper.get_connector_pos(); + draw_line(ripper.junction->position, connector_pos, c); + if(ripper.connection_count < 1) { + draw_box(connector_pos, 0.25_mm, c); + } + auto extents = draw_text(connector_pos+Coordi(0, 0.125_mm), 1.25_mm, ripper.bus_member->name, Orientation::RIGHT, TextPlacement::BASELINE, c); + targets.emplace(ripper.uuid, ObjectType::BUS_RIPPER, connector_pos); + selectables.append(ripper.uuid, ObjectType::BUS_RIPPER, connector_pos, extents.first, extents.second, Selectable::Enlarge::FORCE); + } + + void Canvas::render(const Warning &warn) { + draw_error(warn.position, 2e5, warn.text); + } + + static const Coordf coordf_from_pt(const TPPLPoint &p) { + Coordf r; + r.x = p.x; + r.y = p.y; + return r; + } + + void Canvas::render(const Polygon &ipoly, bool interactive) { + Polygon poly = ipoly.remove_arcs(64); + img_polygon(poly); + if(img_mode) + return; + Color c(1,1,0); + c = core->get_layers().at(poly.layer).color; + if(layer_display.count(poly.layer)) { + c = layer_display.at(poly.layer).color; + } + + TPPLPoly po; + po.Init(poly.vertices.size()); + po.SetHole(false); + + const Polygon::Vertex *v_last = nullptr; + unsigned int i = 0; + for(const auto &it: poly.vertices) { + if(v_last) { + draw_line(v_last->position, it.position, c); + } + po[i].x = it.position.x; + po[i].y = it.position.y; + v_last = ⁢ + i++; + } + draw_line(poly.vertices.front().position, poly.vertices.back().position, c); + + bool draw_tris = !(layer_display.count(poly.layer) && layer_display.at(poly.layer).mode == LayerDisplay::Mode::OUTLINE); + bool hatch = (layer_display.count(poly.layer) && layer_display.at(poly.layer).mode == LayerDisplay::Mode::HATCH); + + if(draw_tris) { + po.SetOrientation(TPPL_CCW); + + std::list outpolys; + TPPLPartition part; + part.Triangulate_EC(&po, &outpolys); + + for(auto &tri: outpolys) { + assert(tri.GetNumPoints() ==3); + Coordf p0 = transform.transform(coordf_from_pt(tri[0])); + Coordf p1 = transform.transform(coordf_from_pt(tri[1])); + Coordf p2 = transform.transform(coordf_from_pt(tri[2])); + triangles.emplace_back(p0, p1, p2, c, hatch); + } + } + if(interactive && !ipoly.temp) { + v_last = nullptr; + i = 0; + for(const auto &it: ipoly.vertices) { + if(v_last) { + auto center = (v_last->position + it.position)/2; + if(v_last->type != Polygon::Vertex::Type::ARC) { + selectables.append(poly.uuid, ObjectType::POLYGON_EDGE, center, v_last->position, it.position, Selectable::Enlarge::OFF, i-1, poly.layer); + + targets.emplace(poly.uuid, ObjectType::POLYGON_EDGE, center, i-1); + } + } + //draw_cross(it.position, 0.25_mm, c); + selectables.append(poly.uuid, ObjectType::POLYGON_VERTEX, it.position, Selectable::Enlarge::OFF, i, poly.layer); + targets.emplace(poly.uuid, ObjectType::POLYGON_VERTEX, it.position, i); + if(it.type == Polygon::Vertex::Type::ARC) { + //draw_plus(it.arc_center, 0.25_mm, c); + selectables.append(poly.uuid, ObjectType::POLYGON_ARC_CENTER, it.arc_center, Selectable::Enlarge::OFF, i, poly.layer); + targets.emplace(poly.uuid, ObjectType::POLYGON_ARC_CENTER, it.arc_center, i); + } + v_last = ⁢ + i++; + } + if(ipoly.vertices.back().type != Polygon::Vertex::Type::ARC) { + auto center = (ipoly.vertices.front().position + ipoly.vertices.back().position)/2; + targets.emplace(poly.uuid, ObjectType::POLYGON_EDGE, center, i-1); + selectables.append(poly.uuid, ObjectType::POLYGON_EDGE, center, ipoly.vertices.front().position, ipoly.vertices.back().position, Selectable::Enlarge::OFF, i-1, poly.layer); + } + } + + } + + void Canvas::render(const Hole &hole, bool interactive) { + Color co(1,1,1); + img_hole(hole); + if(img_mode) + return; + + draw_arc(hole.position, hole.diameter/2, 0, 2*M_PI, co); + if(hole.plated) { + draw_arc(hole.position, 0.9*hole.diameter/2, 0, 2*M_PI, co); + } + float x = hole.diameter/2/M_SQRT2; + draw_line(hole.position+Coordi(-x, -x), hole.position+Coordi(x,x), co); + draw_line(hole.position+Coordi(x, -x), hole.position+Coordi(-x,x), co); + if(interactive) + selectables.append(hole.uuid, ObjectType::HOLE, hole.position, Selectable::Enlarge::OFF); + } + + void Canvas::render(const Pad &pad, int layer) { + transform_save(); + transform.accumulate(pad.placement); + img_net(pad.net); + render(pad.padstack, layer, false); + img_net(nullptr); + transform_restore(); + } + + + void Canvas::render(const Symbol &sym, bool on_sheet, bool smashed) { + if(!on_sheet) { + for(const auto &it: sym.junctions) { + render(it.second, !on_sheet); + } + } + for(const auto &it: sym.lines) { + render(it.second, !on_sheet); + } + for(const auto &it: sym.pins) { + render(it.second, !on_sheet); + } + for(const auto &it: sym.arcs) { + render(it.second, !on_sheet); + } + if(!smashed) { + for(const auto &it: sym.texts) { + render(it.second, !on_sheet); + } + } + } + void Canvas::render(const Sheet &sheet) { + for(const auto &it: sheet.junctions) { + render(it.second); + } + for(const auto &it: sheet.symbols) { + render(it.second); + } + for(const auto &it: sheet.net_lines) { + render(it.second); + } + for(const auto &it: sheet.texts) { + render(it.second); + } + for(const auto &it: sheet.net_labels) { + render(it.second); + } + for(const auto &it: sheet.power_symbols) { + render(it.second); + } + for(const auto &it: sheet.warnings) { + render(it); + } + for(const auto &it: sheet.bus_labels) { + render(it.second); + } + for(const auto &it: sheet.bus_rippers) { + render(it.second); + } + render(sheet.frame); + } + + void Canvas::render(const Frame &frame) { + if(frame.format == Frame::Format::NONE) + return; + int64_t width = frame.get_width(); + int64_t height = frame.get_height(); + int64_t border = frame.border; + Coordf bl(border, border); + Coordf br(width-border, border); + Coordf tr(width-border, height-border); + Coordf tl(border, height-border); + + Color c(0,.5,.0); + draw_line(bl, br, c); + draw_line(br, tr, c); + draw_line(tr, tl, c); + draw_line(tl, bl, c); + } + + void Canvas::render(const Padstack &padstack, int layer, bool interactive) { + if(layer == 10000) { + for(const auto &it: padstack.holes) { + render(it.second, interactive); + } + img_padstack(padstack); + } + for(const auto &it: padstack.polygons) { + if(it.second.layer == layer) + render(it.second, interactive); + } + } + + void Canvas::render(const Padstack &padstack, bool interactive) { + auto layers_sorted = core->get_layers_sorted(); + for(const auto &la: layers_sorted) { + if(la == work_layer) + continue; + if(layer_display.count(la)) + if(!layer_display.at(la).visible) + continue; + render(padstack, la, interactive); + } + render(padstack, work_layer, interactive); + render(padstack, 10000, interactive); + } + + void Canvas::render(const Package &pkg, int layer, bool interactive) { + if(layer == 10000) { + if(interactive) { + for(const auto &it: pkg.junctions) { + render(it.second, interactive); + } + } + for(const auto &it: pkg.pads) { + transform_save(); + transform.accumulate(it.second.placement); + auto bb = it.second.padstack.get_bbox(); + bb.first = transform.transform(bb.first); + bb.second = transform.transform(bb.second); + transform.reset(); + Coordi a(std::min(bb.first.x, bb.second.x), std::min(bb.first.y, bb.second.y)); + Coordi b(std::max(bb.first.x, bb.second.x), std::max(bb.first.y, bb.second.y)); + { + Coordi text_pos = {a.x, (a.y+b.y)/2+abs(a.y-b.y)/4}; + auto text_bb = draw_text(text_pos, 1_mm, it.second.name, Orientation::RIGHT, TextPlacement::CENTER, {1,1,1}, true, 0, false); + float scale_x = (text_bb.second.x-text_bb.first.x)/(float)(b.x-a.x); + float scale_y = ((text_bb.second.y-text_bb.first.y)*2)/(float)(b.y-a.y); + float sc = std::max(scale_x, scale_y); + text_pos.x += (b.x-a.x)/2-(text_bb.second.x-text_bb.first.x)/(2*sc); + + + draw_text(text_pos, 0.8_mm/sc, it.second.name, Orientation::RIGHT, TextPlacement::CENTER, {1,1,1}); + } + if(it.second.net) { + Coordi text_pos = {a.x, (a.y+b.y)/2-abs(a.y-b.y)/4}; + auto text_bb = draw_text(text_pos, 1_mm, it.second.net->name, Orientation::RIGHT, TextPlacement::CENTER, {1,1,1}, true, 0, false); + float scale_x = (text_bb.second.x-text_bb.first.x)/(float)(b.x-a.x); + float scale_y = ((text_bb.second.y-text_bb.first.y)*2)/(float)(b.y-a.y); + float sc = std::max(scale_x, scale_y); + text_pos.x += (b.x-a.x)/2-(text_bb.second.x-text_bb.first.x)/(2*sc); + + draw_text(text_pos, 0.8_mm/sc, it.second.net->name, Orientation::RIGHT, TextPlacement::CENTER, {1,1,1}); + } + transform_restore(); + } + } + for(const auto &it: pkg.lines) { + if(it.second.layer == layer) + render(it.second, interactive); + } + for(const auto &it: pkg.texts) { + if(it.second.layer == layer) + render(it.second, interactive); + } + for(const auto &it: pkg.arcs) { + if(it.second.layer == layer) + render(it.second, interactive); + } + for(const auto &it: pkg.pads) { + render(it.second, layer); + } + for(const auto &it: pkg.polygons) { + if(it.second.layer == layer) + render(it.second, interactive); + } + } + + void Canvas::render(const Package &pkg, bool interactive) { + auto layers_sorted = core->get_layers_sorted(); + for(const auto &la: layers_sorted) { + if(la == work_layer) + continue; + if(layer_display.count(la)) + if(!layer_display.at(la).visible) + continue; + render(pkg, la, interactive); + } + render(pkg, work_layer, interactive); + render(pkg, 10000, interactive); + std::set pad_names; + for(const auto &it: pkg.pads) { + auto x = pad_names.insert(it.second.name); + if(!x.second) { + draw_error(it.second.placement.shift, 2e5, "duplicate pad name"); + } + transform_save(); + transform.accumulate(it.second.placement); + auto bb = it.second.padstack.get_bbox(); + selectables.append(it.second.uuid, ObjectType::PAD, {0,0}, bb.first, bb.second, Selectable::Enlarge::OFF); + transform_restore(); + targets.emplace(it.second.uuid, ObjectType::PAD, it.second.placement.shift); + } + } + + void Canvas::render(const Buffer &buf, int layer) { + if(layer == 10000) { + for(const auto &it: buf.junctions) { + render(it.second); + } + for(const auto &it: buf.pins) { + render(it.second); + } + for(const auto &it: buf.holes) { + render(it.second); + } + } + for(const auto &it: buf.lines) { + if(it.second.layer == layer) + render(it.second); + } + for(const auto &it: buf.texts) { + if(it.second.layer == layer) + render(it.second); + } + for(const auto &it: buf.arcs) { + if(it.second.layer == layer) + render(it.second); + } + for(const auto &it: buf.pads) { + render(it.second, layer); + } + } + + void Canvas::render(const Buffer &buf) { + auto layers_sorted = core->get_layers_sorted(); + for(const auto &la: layers_sorted) { + render(buf, la); + } + render(buf, 10000); + } + + void Canvas::render(const BoardPackage &pkg, int layer) { + transform = pkg.placement; + if(pkg.flip) { + transform.angle *= -1; + while(transform.angle<0) + transform.angle += 65536; + } + if(layer == 10000) { + //targets.emplace(pkg.uuid, ObjectType::BOARD_PACKAGE, pkg.placement.shift); + auto bb = pkg.package.get_bbox(); + selectables.append(pkg.uuid, ObjectType::BOARD_PACKAGE, {0,0}, bb.first, bb.second, Selectable::Enlarge::OFF); + for(const auto &it: pkg.package.pads) { + targets.emplace(UUIDPath<2>(pkg.uuid, it.first), ObjectType::PAD, transform.transform(it.second.placement.shift)); + } + + } + + render(pkg.package, layer, false); + + transform.reset(); + } + + void Canvas::render(const Via &via, int layer) { + transform_save(); + transform.reset(); + transform.shift = via.junction->position; + if(layer == 10000) { + auto bb = via.padstack.get_bbox(); + selectables.append(via.uuid, ObjectType::VIA, {0,0}, bb.first, bb.second, Selectable::Enlarge::OFF); + } + img_net(via.junction->net); + render(via.padstack, layer, false); + img_net(nullptr); + transform_restore(); + } + + void Canvas::render(const Board &brd, int layer) { + if(layer == 10000) { + for(const auto &it: brd.holes) { + render(it.second); + } + for(const auto &it: brd.junctions) { + render(it.second); + } + for(const auto &it: brd.airwires) { + render(it.second); + } + } + for(const auto &it: brd.polygons) { + if(it.second.layer == layer) + render(it.second); + } + for(const auto &it: brd.texts) { + if(it.second.layer == layer) + render(it.second); + } + for(const auto &it: brd.tracks) { + if(it.second.layer == layer) + render(it.second); + } + for(const auto &it: brd.packages) { + render(it.second, layer); + } + for(const auto &it: brd.vias) { + render(it.second, layer); + } + } + + void Canvas::render(const Board &brd) { + clock_t begin = clock(); + auto layers_sorted = core->get_layers_sorted(); + for(const auto &la: layers_sorted) { + if(la == work_layer) + continue; + if(layer_display.count(la)) + if(!layer_display.at(la).visible) + continue; + render(brd, la); + } + render(brd, work_layer); + render(brd, 10000); + for(const auto &path: brd.obstacles) { + for(auto it=path.cbegin(); itX, b->Y), Coordf(it->X, it->Y), {1,0,1}, false, 0); + } + } + } + + unsigned int i = 0; + for(auto it=brd.track_path.cbegin(); itX, b->Y),Coordf(it->X, it->Y), {1,0,1}, false, 0); + } + if(i%2==0) { + draw_line(Coordf(it->X, it->Y), Coordf(it->X, it->Y), {1,0,1}, false, .1_mm); + } + i++; + } + for(const auto &it: brd.warnings) { + render(it); + } + + + clock_t end = clock(); + double elapsed_secs = double(end - begin) / CLOCKS_PER_SEC; + } +} diff --git a/canvas/selectables.cpp b/canvas/selectables.cpp new file mode 100644 index 000000000..e07aee0c2 --- /dev/null +++ b/canvas/selectables.cpp @@ -0,0 +1,104 @@ +#include "box_selection.hpp" +#include "gl_util.hpp" +#include "canvas.hpp" +#include + +namespace horizon { + Selectables::Selectables(class Canvas *c): ca(c) { + } + + void Selectables::append(const UUID &uu, ObjectType ot, const Coordf ¢er, const Coordf &a, const Coordf &b, Selectable::Enlarge enlarge, unsigned int vertex, int layer) { + items.emplace_back(ca->transform.transform(center), ca->transform.transform(a), ca->transform.transform(b), enlarge); + items_ref.emplace_back(uu, ot, vertex, layer); + } + + void Selectables::append(const UUID &uu, ObjectType ot, const Coordf ¢er, Selectable::Enlarge enlarge, unsigned int vertex, int layer) { + append(uu, ot, center, center, center, enlarge, vertex, layer); + } + + static GLuint create_vao (GLuint program, GLuint &vbo_out) { + GLuint origin_index = glGetAttribLocation (program, "origin"); + GLuint bb_index = glGetAttribLocation (program, "bb"); + GLuint flags_index = glGetAttribLocation (program, "flags"); + GLuint vao, buffer; + + /* we need to create a VAO to store the other buffers */ + glGenVertexArrays (1, &vao); + glBindVertexArray (vao); + + /* this is the VBO that holds the vertex data */ + glGenBuffers (1, &buffer); + glBindBuffer (GL_ARRAY_BUFFER, buffer); + //data is buffered lateron + + + GLfloat vertices[] = { + // Position + 7500000, 5000000, 2500000, 3000000, 10000000, 12500000, + 7500000, 2500000, 2500000, 3000000, 10000000, 12500000, + }; + glBufferData (GL_ARRAY_BUFFER, sizeof (vertices), vertices, GL_STATIC_DRAW); + + /* enable and set the position attribute */ + glEnableVertexAttribArray (origin_index); + glVertexAttribPointer (origin_index, 2, GL_FLOAT, GL_FALSE, + sizeof(Selectable), + 0); + glEnableVertexAttribArray (bb_index); + glVertexAttribPointer (bb_index, 4, GL_FLOAT, GL_FALSE, + sizeof(Selectable), + (void*)(2*sizeof (GLfloat))); + glEnableVertexAttribArray (flags_index); + glVertexAttribIPointer (flags_index, 1, GL_UNSIGNED_BYTE, + sizeof(Selectable), + (void*)(6*sizeof (GLfloat))); + + /* enable and set the color attribute */ + /* reset the state; we will re-enable the VAO when needed */ + glBindBuffer (GL_ARRAY_BUFFER, 0); + glBindVertexArray (0); + + //glDeleteBuffers (1, &buffer); + vbo_out = buffer; + return vao; + } + + SelectablesRenderer::SelectablesRenderer(CanvasGL *c, Selectables *s) : ca(c), sel(s) {} + + void SelectablesRenderer::realize() { + program = gl_create_program_from_resource("/net/carrotIndustries/horizon/canvas/shaders/selectable-vertex.glsl", "/net/carrotIndustries/horizon/canvas/shaders/selectable-fragment.glsl", "/net/carrotIndustries/horizon/canvas/shaders/selectable-geometry.glsl"); + vao = create_vao(program, vbo); + GET_LOC(this, screenmat); + GET_LOC(this, scale); + GET_LOC(this, offset); + } + + void SelectablesRenderer::render() { + glUseProgram(program); + glBindVertexArray (vao); + glUniformMatrix3fv(screenmat_loc, 1, GL_TRUE, ca->screenmat.data()); + glUniform1f(scale_loc, ca->scale); + glUniform2f(offset_loc, ca->offset.x, ca->offset.y); + + glDrawArrays (GL_POINTS, 0, sel->items.size()); + + + glBindVertexArray (0); + glUseProgram (0); + } + + void SelectablesRenderer::push() { + /*std::cout << "---" << std::endl; + for(const auto it: items) { + std::cout << it.x << " "<< it.y << " " << (int)it.flags << std::endl; + } + std::cout << "---" << std::endl;*/ + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(Selectable)*sel->items.size(), sel->items.data(), GL_STREAM_DRAW); + } + + void Selectables::clear() { + items.clear(); + items_ref.clear(); + } +} diff --git a/canvas/selectables.hpp b/canvas/selectables.hpp new file mode 100644 index 000000000..478c9d940 --- /dev/null +++ b/canvas/selectables.hpp @@ -0,0 +1,135 @@ +#pragma once +#include "common.hpp" +#include "uuid.hpp" +#include + +namespace horizon { + class Selectable { + public: + enum class Enlarge {AUTO, FORCE, OFF}; + float x; + float y; + float a_x; + float a_y; + float b_x; + float b_y; + uint8_t flags; + + Selectable(const Coordf ¢er, const Coordf &a, const Coordf &b) : + x(center.x), + y(center.y), + a_x(a.x), + a_y(a.y), + b_x(b.x), + b_y(b.y), + flags(0) + {fix_rect();} + Selectable(const Coordf ¢er, const Coordf &a, const Coordf &b, Enlarge enlarge) : + x(center.x), + y(center.y), + a_x(a.x), + a_y(a.y), + b_x(b.x), + b_y(b.y), + flags(0) + { + fix_rect(); + if(enlarge==Enlarge::AUTO || enlarge==Enlarge::FORCE) { + if(((b_x - a_x) < 500000) || enlarge==Enlarge::FORCE) { + a_x -= 250000; + b_x += 250000; + } + if(((b_y - a_y) < 500000) || enlarge==Enlarge::FORCE) { + a_y -= 250000; + b_y += 250000; + } + } + } + bool inside(const Coordf &c, float expand=0) const { + return (c.x >= a_x-expand) && (c.x <= b_x+expand) && (c.y >= a_y-expand) && (c.y <= b_y+expand); + } + float area() const { + return abs(a_x-b_x)*abs(a_y-b_y); + } + private: + void fix_rect() { + if(a_x > b_x) { + float t = a_x; + a_x = b_x; + b_x = t; + } + if(a_y > b_y) { + float t = a_y; + a_y = b_y; + b_y = t; + } + } + } __attribute__((packed)); + + class SelectableRef { + public : + const UUID uuid; + const ObjectType type; + const unsigned int vertex; + const int layer; + SelectableRef(const UUID &uu, ObjectType ty, unsigned int v=0, int la=10000): uuid(uu), type(ty), vertex(v), layer(la){} + bool operator< (const SelectableRef &other) const { + if(type < other.type) { + return true; + } + if(type > other.type) { + return false; + } + if(uuidother.uuid) { + return false; + } + return vertex < other.vertex; + } + bool operator== (const SelectableRef &other) const {return (uuid==other.uuid) && (vertex==other.vertex) && (type == other.type);} + }; + + class Selectables { + friend class Canvas; + friend class CanvasGL; + friend class BoxSelection; + friend class SelectablesRenderer; + public: + Selectables(class Canvas *ca); + void clear(); + void append(const UUID &uu, ObjectType ot, const Coordf ¢er, const Coordf &a, const Coordf &b, Selectable::Enlarge enlarge = Selectable::Enlarge::OFF, unsigned int vertex=0, int layer=10000); + void append(const UUID &uu, ObjectType ot, const Coordf ¢er, Selectable::Enlarge enlarge = Selectable::Enlarge::OFF, unsigned int vertex=0, int layer=10000); + + private: + Canvas *ca; + std::vector items; + std::vector items_ref; + + + }; + + class SelectablesRenderer { + public: + SelectablesRenderer(class CanvasGL *ca, Selectables *sel); + void realize(); + void render(); + void push(); + + private : + CanvasGL *ca; + Selectables *sel; + + GLuint program; + GLuint vao; + GLuint vbo; + + GLuint screenmat_loc; + GLuint scale_loc; + GLuint offset_loc; + + }; +} + + diff --git a/canvas/selection_filter.cpp b/canvas/selection_filter.cpp new file mode 100644 index 000000000..d308ace1f --- /dev/null +++ b/canvas/selection_filter.cpp @@ -0,0 +1,16 @@ +#include "selection_filter.hpp" +#include "canvas.hpp" + +namespace horizon { + bool SelectionFilter::can_select(const SelectableRef &sel) { + if(object_filter.count(sel.type)) + if(!object_filter.at(sel.type)) + return false; + if(sel.layer == 10000) + return true; + if(work_layer_only && (ca->work_layer != sel.layer)) + return false; + + return true; + } +} diff --git a/canvas/selection_filter.hpp b/canvas/selection_filter.hpp new file mode 100644 index 000000000..272226777 --- /dev/null +++ b/canvas/selection_filter.hpp @@ -0,0 +1,17 @@ +#pragma once +#include "selectables.hpp" +#include + +namespace horizon { + class SelectionFilter { + public: + SelectionFilter(Canvas *c): ca(c) {} + bool can_select(const SelectableRef &sel); + + bool work_layer_only = false; + std::map object_filter; + private: + Canvas *ca; + }; + +} diff --git a/canvas/shaders/grid-fragment.glsl b/canvas/shaders/grid-fragment.glsl new file mode 100644 index 000000000..6a020c749 --- /dev/null +++ b/canvas/shaders/grid-fragment.glsl @@ -0,0 +1,7 @@ +#version 330 +uniform vec3 color; +out vec4 outputColor; + +void main() { + outputColor = vec4(color,1); +} diff --git a/canvas/shaders/grid-vertex.glsl b/canvas/shaders/grid-vertex.glsl new file mode 100644 index 000000000..25b29ac6f --- /dev/null +++ b/canvas/shaders/grid-vertex.glsl @@ -0,0 +1,18 @@ +#version 330 +in vec2 position; +uniform mat3 screenmat; +uniform float scale; +uniform vec2 offset; +uniform float grid_size; +uniform vec2 grid_0; +uniform int grid_mod; +uniform float mark_size; + +void main() { + vec2 pos; + int gr_x = int(mod(gl_InstanceID, grid_mod)); + int gr_y = int(gl_InstanceID/grid_mod); + pos.x = scale*(grid_0.x+grid_size*gr_x)+offset.x+position.x*mark_size; + pos.y = -scale*(grid_0.y+grid_size*gr_y)+offset.y+position.y*mark_size; + gl_Position = vec4((screenmat*vec3(pos, 1)), 1); +} diff --git a/canvas/shaders/selectable-fragment.glsl b/canvas/shaders/selectable-fragment.glsl new file mode 100644 index 000000000..f019eb6fe --- /dev/null +++ b/canvas/shaders/selectable-fragment.glsl @@ -0,0 +1,7 @@ +#version 330 +out vec4 outputColor; +in vec3 color_to_fragment; + +void main() { + outputColor = vec4(color_to_fragment ,1); +} diff --git a/canvas/shaders/selectable-geometry.glsl b/canvas/shaders/selectable-geometry.glsl new file mode 100644 index 000000000..5c37ea0d7 --- /dev/null +++ b/canvas/shaders/selectable-geometry.glsl @@ -0,0 +1,114 @@ +#version 330 +layout(points) in; +layout(triangle_strip, max_vertices = 32) out; +uniform mat3 screenmat; +uniform float scale; +uniform vec2 offset; +in vec2 origin_to_geom[1]; +in vec4 bb_to_geom[1]; +in uint flags_to_geom[1]; +out vec3 color_to_fragment; + +vec4 t(vec2 p) { + return vec4((screenmat*vec3(scale*p.x+offset.x , -scale*p.y+offset.y, 1)), 1); +} + +void fbox(vec2 bl, vec2 tr) { + gl_Position = t(bl); + EmitVertex(); + + gl_Position = t(vec2(tr.x, bl.y)); + EmitVertex(); + + gl_Position = t(vec2(bl.x, tr.y)); + EmitVertex(); + + gl_Position = t(tr); + EmitVertex(); + + EndPrimitive(); +} + + +void hline(float x, float y0, float y1) { + float w = 1/scale; + fbox(vec2(x-w, y0), vec2(x+w, y1)); +} + +void vline(float y, float x0, float x1) { + float w = 1/scale; + fbox(vec2(x0, y-w), vec2(x1, y+w)); +} + +void hbox(vec2 bl, vec2 tr) { + hline(bl.x, bl.y, tr.y); + hline(tr.x, bl.y, tr.y); + vline(bl.y, bl.x, tr.x); + vline(tr.y, bl.x, tr.x); + +} + +void main() { + float origin_size = 10/scale; + vec2 p = origin_to_geom[0]; + vec2 bb_bl = bb_to_geom[0].xy; + vec2 bb_tr = bb_to_geom[0].zw; + + const float min_sz = 10; + if(abs(bb_tr.x - bb_bl.x) < min_sz/scale) { + bb_tr.x += .5*min_sz/scale; + bb_bl.x -= .5*min_sz/scale; + } + if(abs(bb_tr.y - bb_bl.y) < min_sz/scale) { + bb_tr.y += .5*min_sz/scale; + bb_bl.y -= .5*min_sz/scale; + } + uint flags = flags_to_geom[0]; + if(flags == uint(0)) { + return; + } + color_to_fragment = vec3(1,0,1); + if((flags & uint(2))!=uint(0)) { //prelight + color_to_fragment = vec3(.5,0,.5); + } + vec3 c_save = color_to_fragment; + + float os = origin_size; + + gl_Position = t(p+vec2(0, os)); + EmitVertex(); + + gl_Position = t(p+vec2(os, 0)); + EmitVertex(); + + gl_Position = t(p+vec2(-os, 0)); + EmitVertex(); + + gl_Position = t(p+vec2(0, -os)); + EmitVertex(); + EndPrimitive(); + + os*=.5; + color_to_fragment = vec3(0,0,0); + + gl_Position = t(p+vec2(0, os)); + EmitVertex(); + + gl_Position = t(p+vec2(os, 0)); + EmitVertex(); + + gl_Position = t(p+vec2(-os, 0)); + EmitVertex(); + + gl_Position = t(p+vec2(0, -os)); + EmitVertex(); + + EndPrimitive(); + + color_to_fragment = c_save; + + hbox(bb_bl, bb_tr); + + EndPrimitive(); + +} diff --git a/canvas/shaders/selectable-vertex.glsl b/canvas/shaders/selectable-vertex.glsl new file mode 100644 index 000000000..aa9a346ee --- /dev/null +++ b/canvas/shaders/selectable-vertex.glsl @@ -0,0 +1,17 @@ +#version 330 +in vec2 origin; +in vec4 bb; +in uint flags; + +out vec2 origin_to_geom; +out vec4 bb_to_geom; +out uint flags_to_geom; + +void main() { + + origin_to_geom = origin; + bb_to_geom = bb; + flags_to_geom = flags; + + +} diff --git a/canvas/shaders/selection-fragment.glsl b/canvas/shaders/selection-fragment.glsl new file mode 100644 index 000000000..63881bd00 --- /dev/null +++ b/canvas/shaders/selection-fragment.glsl @@ -0,0 +1,17 @@ +#version 330 +out vec4 outputColor; +in vec2 x; +in vec2 dim; +uniform float scale; + +void main() { + float border = 3/scale; + float alpha = .2; + if((x.x < border) || (x.x > dim.x-border)) { + alpha = 1; + } + if((x.y < border) || (x.y > dim.y-border)) { + alpha = 1; + } + outputColor = vec4(1,0,0,alpha); +} diff --git a/canvas/shaders/selection-vertex.glsl b/canvas/shaders/selection-vertex.glsl new file mode 100644 index 000000000..70a1e3af5 --- /dev/null +++ b/canvas/shaders/selection-vertex.glsl @@ -0,0 +1,33 @@ +#version 330 +uniform mat3 screenmat; +uniform float scale; +uniform vec2 offset; +out vec2 x; +out vec2 dim; +uniform vec2 a,b; + +void main() { + + vec2 bl = min(a,b); + vec2 tr = max(a,b); + vec2 t = vec2(0,0); + if(gl_VertexID == 0) { + t = vec2(bl.x, bl.y); + } + else if(gl_VertexID == 1) { + t = vec2(bl.x, tr.y); + } + else if(gl_VertexID == 2) { + t = vec2(tr.x, bl.y); + } + else if(gl_VertexID == 3) { + t = vec2(tr.x, tr.y); + } + dim = abs(tr - bl); + x=t-bl; + + vec2 pos; + pos.x = scale*t.x+offset.x; + pos.y = -scale*t.y+offset.y; + gl_Position = vec4((screenmat*vec3(pos, 1)), 1); +} diff --git a/canvas/shaders/triangle-fragment.glsl b/canvas/shaders/triangle-fragment.glsl new file mode 100644 index 000000000..4c9236258 --- /dev/null +++ b/canvas/shaders/triangle-fragment.glsl @@ -0,0 +1,38 @@ +#version 330 +uniform float scale; +out vec4 outputColor; +smooth in vec3 color_to_fragment; +smooth in float striper_to_fragment; +smooth in float alpha_to_fragment; +smooth in vec2 round_pos_to_fragment; +flat in float line_length_to_fragment; +flat in float line_height_px_to_fragment; +flat in int flags_to_fragment; + +void main() { + float alpha = alpha_to_fragment; + bool disc = false; + if(mod(striper_to_fragment,20)>2 || (flags_to_fragment&2)!=0) { + disc = true; + } + if(abs(round_pos_to_fragment.x)>(line_length_to_fragment/2)) { + if(length(abs(round_pos_to_fragment)-vec2(line_length_to_fragment/2,0))>1) { + disc = true; + } + else { + if((1-length(abs(round_pos_to_fragment)-vec2(line_length_to_fragment/2,0)))*line_height_px_to_fragment<1) { + disc = false; + alpha = 1; + } + } + } + else { + if((1-abs(round_pos_to_fragment.y))*line_height_px_to_fragment<(1)) { + disc = false; + alpha = 1; + } + } + if(disc) + discard; + outputColor = vec4(color_to_fragment, alpha); +} diff --git a/canvas/shaders/triangle-geometry.glsl b/canvas/shaders/triangle-geometry.glsl new file mode 100644 index 000000000..c69d80bad --- /dev/null +++ b/canvas/shaders/triangle-geometry.glsl @@ -0,0 +1,105 @@ +#version 330 +layout(points) in; +layout(triangle_strip, max_vertices = 64) out; +uniform mat3 screenmat; +uniform float scale; +uniform vec2 offset; +uniform float alpha; +in vec2 p0_to_geom[1]; +in vec2 p1_to_geom[1]; +in vec2 p2_to_geom[1]; +in vec3 color_to_geom[1]; +in int flags_to_geom[1]; +smooth out vec3 color_to_fragment; +smooth out float striper_to_fragment; +smooth out float alpha_to_fragment; +smooth out vec2 round_pos_to_fragment; +flat out float line_length_to_fragment; +flat out float line_height_px_to_fragment; +flat out int flags_to_fragment; + + +#define PI 3.1415926535897932384626433832795 + +vec4 t(vec2 p) { + return vec4((screenmat*vec3(scale*p.x+offset.x , -scale*p.y+offset.y, 1)), 1); +} + +float t2(vec2 p) { + if((flags_to_geom[0]&1)!=0) + return scale*p.x -scale*p.y; + return 0.0; +} + +vec2 p2r(float phi, float l) { + return vec2(cos(phi), sin(phi))*l; +} + +void main() { + vec2 p0 = p0_to_geom[0]; + vec2 p1 = p1_to_geom[0]; + vec2 p2 = p2_to_geom[0]; + vec3 color = color_to_geom[0]; + color_to_fragment = color; + flags_to_fragment = flags_to_geom[0]; + if(!isnan(p2.y)) { + round_pos_to_fragment = vec2(0,0); + line_height_px_to_fragment = 10; + + gl_Position = t(p0); + striper_to_fragment = t2(p0); + alpha_to_fragment = alpha; + EmitVertex(); + + gl_Position = t(p1); + striper_to_fragment = t2(p1); + alpha_to_fragment = alpha; + EmitVertex(); + + gl_Position = t(p2); + striper_to_fragment = t2(p2); + alpha_to_fragment = alpha; + EmitVertex(); + } + else { + float width = p2.x/2; + + width = max(width, .5/scale); + vec2 v = p1-p0; + vec2 o = vec2(-v.y, v.x); + o /= length(o); + o *= width; + vec2 vw = (v/length(v))*width; + vec2 p0x = p0-vw; + vec2 p1x = p1+vw; + + alpha_to_fragment = alpha; + float l = length(v)/width+2; + line_height_px_to_fragment = width*scale; + line_length_to_fragment = length(v)/width; + + striper_to_fragment = t2(p0x-o); + gl_Position = t(p0x-o); + round_pos_to_fragment = vec2(-l/2,-1); + EmitVertex(); + + striper_to_fragment = t2(p0x+o); + gl_Position = t(p0x+o); + round_pos_to_fragment = vec2(-l/2,1); + EmitVertex(); + + striper_to_fragment = t2(p1x-o); + gl_Position = t(p1x-o); + round_pos_to_fragment = vec2(l/2,-1); + EmitVertex(); + + striper_to_fragment = t2(p1x+o); + gl_Position = t(p1x+o); + round_pos_to_fragment = vec2(l/2,1); + EmitVertex(); + + } + + EndPrimitive(); + +} diff --git a/canvas/shaders/triangle-vertex.glsl b/canvas/shaders/triangle-vertex.glsl new file mode 100644 index 000000000..c35374c32 --- /dev/null +++ b/canvas/shaders/triangle-vertex.glsl @@ -0,0 +1,21 @@ +#version 330 +in vec2 p0; +in vec2 p1; +in vec2 p2; +in vec3 color; +in int flags; + +out vec2 p0_to_geom; +out vec2 p1_to_geom; +out vec2 p2_to_geom; +out vec3 color_to_geom; +out int flags_to_geom; + + +void main() { + p0_to_geom = p0; + p1_to_geom = p1; + p2_to_geom = p2; + color_to_geom = color; + flags_to_geom = flags; +} diff --git a/canvas/target.hpp b/canvas/target.hpp new file mode 100644 index 000000000..1a90683a9 --- /dev/null +++ b/canvas/target.hpp @@ -0,0 +1,32 @@ +#pragma once +#include "uuid_path.hpp" +#include "common.hpp" + +namespace horizon { + class Target { + public : + UUIDPath<2> path; + ObjectType type; + Coordi p; + unsigned int vertex = 0; + Target(const UUIDPath<2> &uu, ObjectType ot, const Coordi &pi, unsigned int v=0) : path(uu), type(ot), p(pi), vertex(v) {}; + Target() :type(ObjectType::INVALID) {}; + bool is_valid() const {return type != ObjectType::INVALID;} + bool operator< (const Target &other) const { + if(type < other.type) { + return true; + } + if(type > other.type) { + return false; + } + if(path + + +namespace horizon { +enum HersheyFonts { + FONT_HERSHEY_SIMPLEX = 0, //!< normal size sans-serif font + FONT_HERSHEY_PLAIN = 1, //!< small size sans-serif font + FONT_HERSHEY_DUPLEX = 2, //!< normal size sans-serif font (more complex than FONT_HERSHEY_SIMPLEX) + FONT_HERSHEY_COMPLEX = 3, //!< normal size serif font + FONT_HERSHEY_TRIPLEX = 4, //!< normal size serif font (more complex than FONT_HERSHEY_COMPLEX) + FONT_HERSHEY_COMPLEX_SMALL = 5, //!< smaller version of FONT_HERSHEY_COMPLEX + FONT_HERSHEY_SCRIPT_SIMPLEX = 6, //!< hand-writing style font + FONT_HERSHEY_SCRIPT_COMPLEX = 7, //!< more complex variant of FONT_HERSHEY_SCRIPT_SIMPLEX + FONT_ITALIC = 16 //!< flag for italic font +}; + + + enum { FONT_SIZE_SHIFT=8, FONT_ITALIC_ALPHA=(1 << 8), + FONT_ITALIC_DIGIT=(2 << 8), FONT_ITALIC_PUNCT=(4 << 8), + FONT_ITALIC_BRACES=(8 << 8), FONT_HAVE_GREEK=(16 << 8), + FONT_HAVE_CYRILLIC=(32 << 8) }; + + static const int HersheyPlain[] = { + (5 + 4*16) + FONT_HAVE_GREEK, + 199, 214, 217, 233, 219, 197, 234, 216, 221, 222, 228, 225, 211, 224, 210, 220, + 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 212, 213, 191, 226, 192, + 215, 190, 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, 193, 84, + 194, 85, 86, 87, 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, + 195, 223, 196, 88 }; + + static const int HersheyPlainItalic[] = { + (5 + 4*16) + FONT_ITALIC_ALPHA + FONT_HAVE_GREEK, + 199, 214, 217, 233, 219, 197, 234, 216, 221, 222, 228, 225, 211, 224, 210, 220, + 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 212, 213, 191, 226, 192, + 215, 190, 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, 193, 84, + 194, 85, 86, 87, 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, + 195, 223, 196, 88 }; + + static const int HersheyComplexSmall[] = { + (6 + 7*16) + FONT_HAVE_GREEK, + 1199, 1214, 1217, 1275, 1274, 1271, 1272, 1216, 1221, 1222, 1219, 1232, 1211, 1231, 1210, 1220, + 1200, 1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209, 1212, 2213, 1241, 1238, 1242, + 1215, 1273, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, + 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1223, 1084, + 1224, 1247, 586, 1249, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1111, + 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1120, 1121, 1122, 1123, 1124, 1125, 1126, + 1225, 1229, 1226, 1246 }; + + static const int HersheyComplexSmallItalic[] = { + (6 + 7*16) + FONT_ITALIC_ALPHA + FONT_HAVE_GREEK, + 1199, 1214, 1217, 1275, 1274, 1271, 1272, 1216, 1221, 1222, 1219, 1232, 1211, 1231, 1210, 1220, + 1200, 1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209, 1212, 1213, 1241, 1238, 1242, + 1215, 1273, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, + 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1223, 1084, + 1224, 1247, 586, 1249, 1151, 1152, 1153, 1154, 1155, 1156, 1157, 1158, 1159, 1160, 1161, + 1162, 1163, 1164, 1165, 1166, 1167, 1168, 1169, 1170, 1171, 1172, 1173, 1174, 1175, 1176, + 1225, 1229, 1226, 1246 }; + + static const int HersheySimplex[] = { + (9 + 12*16) + FONT_HAVE_GREEK, + 2199, 714, 717, 733, 719, 697, 734, 716, 721, 722, 728, 725, 711, 724, 710, 720, + 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 712, 713, 691, 726, 692, + 715, 690, 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, 693, 584, + 694, 2247, 586, 2249, 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, + 695, 723, 696, 2246 }; + + static const int HersheyDuplex[] = { + (9 + 12*16) + FONT_HAVE_GREEK, + 2199, 2714, 2728, 2732, 2719, 2733, 2718, 2727, 2721, 2722, 2723, 2725, 2711, 2724, 2710, 2720, + 2700, 2701, 2702, 2703, 2704, 2705, 2706, 2707, 2708, 2709, 2712, 2713, 2730, 2726, 2731, + 2715, 2734, 2501, 2502, 2503, 2504, 2505, 2506, 2507, 2508, 2509, 2510, 2511, 2512, 2513, + 2514, 2515, 2516, 2517, 2518, 2519, 2520, 2521, 2522, 2523, 2524, 2525, 2526, 2223, 2084, + 2224, 2247, 587, 2249, 2601, 2602, 2603, 2604, 2605, 2606, 2607, 2608, 2609, 2610, 2611, + 2612, 2613, 2614, 2615, 2616, 2617, 2618, 2619, 2620, 2621, 2622, 2623, 2624, 2625, 2626, + 2225, 2229, 2226, 2246 }; + + static const int HersheyComplex[] = { + (9 + 12*16) + FONT_HAVE_GREEK + FONT_HAVE_CYRILLIC, + 2199, 2214, 2217, 2275, 2274, 2271, 2272, 2216, 2221, 2222, 2219, 2232, 2211, 2231, 2210, 2220, + 2200, 2201, 2202, 2203, 2204, 2205, 2206, 2207, 2208, 2209, 2212, 2213, 2241, 2238, 2242, + 2215, 2273, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, + 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026, 2223, 2084, + 2224, 2247, 587, 2249, 2101, 2102, 2103, 2104, 2105, 2106, 2107, 2108, 2109, 2110, 2111, + 2112, 2113, 2114, 2115, 2116, 2117, 2118, 2119, 2120, 2121, 2122, 2123, 2124, 2125, 2126, + 2225, 2229, 2226, 2246, 2801, 2802, 2803, 2804, 2805, 2806, 2807, 2808, 2809, 2810, 2811, + 2812, 2813, 2814, 2815, 2816, 2817, 2818, 2819, 2820, 2821, 2822, 2823, 2824, 2825, 2826, + 2827, 2828, 2829, 2830, 2831, 2832, 2901, 2902, 2903, 2904, 2905, 2906, 2907, 2908, 2909, + 2910, 2911, 2912, 2913, 2914, 2915, 2916, 2917, 2918, 2919, 2920, 2921, 2922, 2923, 2924, + 2925, 2926, 2927, 2928, 2929, 2930, 2931, 2932}; + + static const int HersheyComplexItalic[] = { + (9 + 12*16) + FONT_ITALIC_ALPHA + FONT_ITALIC_DIGIT + FONT_ITALIC_PUNCT + + FONT_HAVE_GREEK + FONT_HAVE_CYRILLIC, + 2199, 2764, 2778, 2782, 2769, 2783, 2768, 2777, 2771, 2772, 2219, 2232, 2211, 2231, 2210, 2220, + 2750, 2751, 2752, 2753, 2754, 2755, 2756, 2757, 2758, 2759, 2212, 2213, 2241, 2238, 2242, + 2765, 2273, 2051, 2052, 2053, 2054, 2055, 2056, 2057, 2058, 2059, 2060, 2061, 2062, 2063, + 2064, 2065, 2066, 2067, 2068, 2069, 2070, 2071, 2072, 2073, 2074, 2075, 2076, 2223, 2084, + 2224, 2247, 587, 2249, 2151, 2152, 2153, 2154, 2155, 2156, 2157, 2158, 2159, 2160, 2161, + 2162, 2163, 2164, 2165, 2166, 2167, 2168, 2169, 2170, 2171, 2172, 2173, 2174, 2175, 2176, + 2225, 2229, 2226, 2246 }; + + static const int HersheyTriplex[] = { + (9 + 12*16) + FONT_HAVE_GREEK, + 2199, 3214, 3228, 3232, 3219, 3233, 3218, 3227, 3221, 3222, 3223, 3225, 3211, 3224, 3210, 3220, + 3200, 3201, 3202, 3203, 3204, 3205, 3206, 3207, 3208, 3209, 3212, 3213, 3230, 3226, 3231, + 3215, 3234, 3001, 3002, 3003, 3004, 3005, 3006, 3007, 3008, 3009, 3010, 3011, 3012, 3013, + 2014, 3015, 3016, 3017, 3018, 3019, 3020, 3021, 3022, 3023, 3024, 3025, 3026, 2223, 2084, + 2224, 2247, 587, 2249, 3101, 3102, 3103, 3104, 3105, 3106, 3107, 3108, 3109, 3110, 3111, + 3112, 3113, 3114, 3115, 3116, 3117, 3118, 3119, 3120, 3121, 3122, 3123, 3124, 3125, 3126, + 2225, 2229, 2226, 2246 }; + + static const int HersheyTriplexItalic[] = { + (9 + 12*16) + FONT_ITALIC_ALPHA + FONT_ITALIC_DIGIT + + FONT_ITALIC_PUNCT + FONT_HAVE_GREEK, + 2199, 3264, 3278, 3282, 3269, 3233, 3268, 3277, 3271, 3272, 3223, 3225, 3261, 3224, 3260, 3270, + 3250, 3251, 3252, 3253, 3254, 3255, 3256, 3257, 3258, 3259, 3262, 3263, 3230, 3226, 3231, + 3265, 3234, 3051, 3052, 3053, 3054, 3055, 3056, 3057, 3058, 3059, 3060, 3061, 3062, 3063, + 2064, 3065, 3066, 3067, 3068, 3069, 3070, 3071, 3072, 3073, 3074, 3075, 3076, 2223, 2084, + 2224, 2247, 587, 2249, 3151, 3152, 3153, 3154, 3155, 3156, 3157, 3158, 3159, 3160, 3161, + 3162, 3163, 3164, 3165, 3166, 3167, 3168, 3169, 3170, 3171, 3172, 3173, 3174, 3175, 3176, + 2225, 2229, 2226, 2246 }; + + static const int HersheyScriptSimplex[] = { + (9 + 12*16) + FONT_ITALIC_ALPHA + FONT_HAVE_GREEK, + 2199, 714, 717, 733, 719, 697, 734, 716, 721, 722, 728, 725, 711, 724, 710, 720, + 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 712, 713, 691, 726, 692, + 715, 690, 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, 693, 584, + 694, 2247, 586, 2249, 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, + 695, 723, 696, 2246 }; + + static const int HersheyScriptComplex[] = { + (9 + 12*16) + FONT_ITALIC_ALPHA + FONT_ITALIC_DIGIT + FONT_ITALIC_PUNCT + FONT_HAVE_GREEK, + 2199, 2764, 2778, 2782, 2769, 2783, 2768, 2777, 2771, 2772, 2219, 2232, 2211, 2231, 2210, 2220, + 2750, 2751, 2752, 2753, 2754, 2755, 2756, 2757, 2758, 2759, 2212, 2213, 2241, 2238, 2242, + 2215, 2273, 2551, 2552, 2553, 2554, 2555, 2556, 2557, 2558, 2559, 2560, 2561, 2562, 2563, + 2564, 2565, 2566, 2567, 2568, 2569, 2570, 2571, 2572, 2573, 2574, 2575, 2576, 2223, 2084, + 2224, 2247, 586, 2249, 2651, 2652, 2653, 2654, 2655, 2656, 2657, 2658, 2659, 2660, 2661, + 2662, 2663, 2664, 2665, 2666, 2667, 2668, 2669, 2670, 2671, 2672, 2673, 2674, 2675, 2676, + 2225, 2229, 2226, 2246 }; + + + static const int* getFontData(int fontFace) + { + bool isItalic = (fontFace & FONT_ITALIC) != 0; + const int* ascii = 0; + + switch( fontFace & 15 ) + { + case FONT_HERSHEY_SIMPLEX: + ascii = HersheySimplex; + break; + case FONT_HERSHEY_PLAIN: + ascii = !isItalic ? HersheyPlain : HersheyPlainItalic; + break; + case FONT_HERSHEY_DUPLEX: + ascii = HersheyDuplex; + break; + case FONT_HERSHEY_COMPLEX: + ascii = !isItalic ? HersheyComplex : HersheyComplexItalic; + break; + case FONT_HERSHEY_TRIPLEX: + ascii = !isItalic ? HersheyTriplex : HersheyTriplexItalic; + break; + case FONT_HERSHEY_COMPLEX_SMALL: + ascii = !isItalic ? HersheyComplexSmall : HersheyComplexSmallItalic; + break; + case FONT_HERSHEY_SCRIPT_SIMPLEX: + ascii = HersheyScriptSimplex; + break; + case FONT_HERSHEY_SCRIPT_COMPLEX: + ascii = HersheyScriptComplex; + break; + } + return ascii; + } + + static void readCheck(char *c, int *i, const char *text, int fontFace) + { + + int leftBoundary = ' ', rightBoundary = 127; + + if(*c >= 0x80 && fontFace == FONT_HERSHEY_COMPLEX) + { + if(*c == 0xD0 && text[*i + 1] >= 0x90 && text[*i + 1] <= 0xBF) + { + *c = text[*(++i)] - 17; + leftBoundary = 127; + rightBoundary = 175; + } + else if(*c == 0xD1 && text[*i + 1] >= 0x80 && text[*i + 1] <= 0x8F) + { + *c = text[*(++i)] + 47; + leftBoundary = 175; + rightBoundary = 191; + } + else + { + if(*c >= 0xC0 && text[*i+1] != 0) //2 bytes utf + *i = *i+1; + + if(*c >= 0xE0 && text[*i+1] != 0) //3 bytes utf + *i = *i+1; + + if(*c >= 0xF0 && text[*i+1] != 0) //4 bytes utf + *i = *i+1; + + if(*c >= 0xF8 && text[*i+1] != 0) //5 bytes utf + *i = *i+1; + + if(*c >= 0xFC && text[*i+1] != 0) //6 bytes utf + *i = *i+1; + + *c = '?'; + } + } + + if(*c >= rightBoundary || *c < leftBoundary) + *c = '?'; + } + + + #define IN_RANGE(x, lo, hi) (((x)>=(lo))&&((x)<=(hi))) + + + static const unsigned int ftab[] = {2198, 714, 717, 733, 719, 697, 734, 716, 721, 722, 728, 725, 711, 724, 710, 720, + 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 712, 713, 691, 726, 692, + 715, 690, 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, 693, 584, + 694, 2247, 586, 2249, 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, + 695, 723, 696, 2246}; + + static unsigned int codepoint_to_hershey(gunichar c) { + int x = c - ' '; + if(x>=0 && x<(sizeof(ftab)/sizeof(ftab[0]))) { + return ftab[x]; + } + else { + switch(c) { + case 0x3bc: return 638; + case 0xb5: return 638; + case 0x2126: return 550; + case 0x3a9: return 550; + + default: return 870; + } + } + } + + + extern const char* hershey_glyphs[]; + + std::pair Canvas::draw_text(const Coordf &p, float size, const std::string &rtext, Orientation orientation, TextPlacement placement, const Color &color, bool tr, uint64_t width, bool draw) { + Glib::ustring text(rtext); + std::vector vtext; + vtext.reserve(text.size()); + for(const auto &it: text) { + vtext.push_back(it); + } + float x0 = p.x; + float y0 = p.y; + int fontFace = FONT_HERSHEY_SIMPLEX; + const int* ascii = getFontData(fontFace); + float sc = size / 21; + float yshift = (placement==TextPlacement::CENTER)?-size/2:(placement==TextPlacement::BOTTOM?size/2:0); + if(orientation == Orientation::LEFT || orientation == Orientation::RIGHT) { + y0 += yshift; + } + else { + x0 -= yshift; + } + int i = 0; + if(orientation == Orientation::LEFT || orientation == Orientation::DOWN) { + std::reverse(vtext.begin(), vtext.end()); + } + + Coordf a = p; + Coordf b = p; + + for(const gunichar c: vtext) { + //char c = ci; + //readCheck(&c, &i, text.data(), fontFace); + const char *s = hershey_glyphs[codepoint_to_hershey(c)]; + + float step; + { + + int left = s[0] - 'R'; + int right = s[1] - 'R'; + size_t n = 0; + int x,y, x2, y2; + for( s += 2;; ) { + if( *s == ' ' || !*s ) { + if( !*s++ ) + break; + n = 0; + } + else { + x = s[0] - 'R'; + y = -(s[1] - 'R')+9; + s += 2; + if(n > 0) { + int xshift; + if(orientation == Orientation::LEFT || orientation == Orientation::DOWN) { + xshift = left; + } + else { + xshift = -left; + } + Coordf p0, p1; + if(orientation == Orientation::LEFT || orientation == Orientation::RIGHT) { + p0 ={x0+(x+xshift)*sc, y0+y*sc}; + p1 = { x0+(x2+xshift)*sc, y0+y2*sc}; + } + else { //UP, DOWN + p0 = {x0-(y*sc), y0+(x+xshift)*sc}; + p1 = {x0-(y2*sc), y0+(x2+xshift)*sc}; + } + if(draw) { + img_text_line(Coordi(p0.x, p0.y), Coordi(p1.x, p1.y), width); + draw_line(p0, p1, color, tr, width); + } + a=Coordf::min(a, Coordf::min(p0, p1)); + b=Coordf::max(b, Coordf::max(p0, p1)); + } + + x2 = x; + y2 = y; + + n++; + } + } + step = (right-left)*sc; + } + switch(orientation) { + case Orientation::LEFT : + x0 -= step; + break; + + case Orientation::RIGHT : + x0 += step; + break; + + case Orientation::UP : + y0 += step; + break; + + case Orientation::DOWN : + y0 -= step; + break; + } + i++; + } + return {a,b}; + } +} diff --git a/canvas/triangle.cpp b/canvas/triangle.cpp new file mode 100644 index 000000000..56f2d9932 --- /dev/null +++ b/canvas/triangle.cpp @@ -0,0 +1,110 @@ +#include "triangle.hpp" +#include "gl_util.hpp" +#include "canvas.hpp" + +namespace horizon { + + + static GLuint create_vao (GLuint program, GLuint &vbo_out) { + auto err = glGetError(); + if(err != GL_NO_ERROR) { + std::cout << "gl error a " << err << std::endl; + } + GLuint p0_index = glGetAttribLocation (program, "p0"); + GLuint p1_index = glGetAttribLocation (program, "p1"); + GLuint p2_index = glGetAttribLocation (program, "p2"); + GLuint color_index = glGetAttribLocation (program, "color"); + GLuint flags_index = glGetAttribLocation (program, "flags"); + + + GLuint vao, buffer; + + /* we need to create a VAO to store the other buffers */ + glGenVertexArrays (1, &vao); + glBindVertexArray (vao); + + /* this is the VBO that holds the vertex data */ + glGenBuffers (1, &buffer); + glBindBuffer (GL_ARRAY_BUFFER, buffer); + //data is buffered lateron + + + GLfloat vertices[] = { + // Position + 0,0, 7500000, 5000000, 2500000, -2500000, 1 ,0,1 + }; + glBufferData (GL_ARRAY_BUFFER, sizeof (vertices), vertices, GL_STATIC_DRAW); + + + + /* enable and set the position attribute */ + glEnableVertexAttribArray (p0_index); + glVertexAttribPointer (p0_index, 2, GL_FLOAT, GL_FALSE, + sizeof(Triangle), + (void*)offsetof(Triangle, x0)); + + glEnableVertexAttribArray (p1_index); + glVertexAttribPointer (p1_index, 2, GL_FLOAT, GL_FALSE, + sizeof(Triangle), + (void*)offsetof(Triangle, x1)); + + glEnableVertexAttribArray (p2_index); + glVertexAttribPointer (p2_index, 2, GL_FLOAT, GL_FALSE, + sizeof(Triangle), + (void*)offsetof(Triangle, x2)); + + glEnableVertexAttribArray (color_index); + glVertexAttribPointer (color_index, 3, GL_FLOAT, GL_FALSE, + sizeof(Triangle), + (void*)offsetof(Triangle, r)); + + glEnableVertexAttribArray (flags_index); + glVertexAttribIPointer (flags_index, 1, GL_UNSIGNED_BYTE, + sizeof(Triangle), + (void*)offsetof(Triangle, flags)); + + + + /* enable and set the color attribute */ + /* reset the state; we will re-enable the VAO when needed */ + glBindBuffer (GL_ARRAY_BUFFER, 0); + glBindVertexArray (0); + + //glDeleteBuffers (1, &buffer); + vbo_out = buffer; + + + return vao; + } + + TriangleRenderer::TriangleRenderer(CanvasGL *c, std::vector &tris) : ca(c), triangles(tris) {} + + void TriangleRenderer::realize() { + program = gl_create_program_from_resource("/net/carrotIndustries/horizon/canvas/shaders/triangle-vertex.glsl", "/net/carrotIndustries/horizon/canvas/shaders/triangle-fragment.glsl", "/net/carrotIndustries/horizon/canvas/shaders/triangle-geometry.glsl"); + vao = create_vao(program, vbo); + GET_LOC(this, screenmat); + GET_LOC(this, scale); + GET_LOC(this, offset); + GET_LOC(this, alpha); + } + + void TriangleRenderer::render() { + glUseProgram(program); + glBindVertexArray (vao); + glUniformMatrix3fv(screenmat_loc, 1, GL_TRUE, ca->screenmat.data()); + glUniform1f(scale_loc, ca->scale); + glUniform1f(alpha_loc, ca->property_layer_opacity()/100); + glUniform2f(offset_loc, ca->offset.x, ca->offset.y); + + glDrawArrays (GL_POINTS, 0, triangles.size()); + + + glBindVertexArray (0); + glUseProgram (0); + } + + void TriangleRenderer::push() { + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(Triangle)*triangles.size(), triangles.data(), GL_STREAM_DRAW); + } +} diff --git a/canvas/triangle.hpp b/canvas/triangle.hpp new file mode 100644 index 000000000..73fc166ed --- /dev/null +++ b/canvas/triangle.hpp @@ -0,0 +1,57 @@ +#pragma once +#include "common.hpp" +#include + +namespace horizon { + + class Triangle { + public: + float x0; + float y0; + float x1; + float y1; + float x2; + float y2; + float r; + float g; + float b; + uint8_t flags; + + Triangle(const Coordf &p0, const Coordf &p1, const Coordf &p2, const Color &co, uint8_t f=0): + x0(p0.x), + y0(p0.y), + x1(p1.x), + y1(p1.y), + x2(p2.x), + y2(p2.y), + r(co.r), + g(co.g), + b(co.b), + flags(f) + {} + } __attribute__((packed)); + + + class TriangleRenderer { + friend class CanvasGL; + public : + TriangleRenderer(class CanvasGL *c, std::vector &tris); + void realize(); + void render(); + void push(); + + private : + CanvasGL *ca; + std::vector &triangles; + + GLuint program; + GLuint vao; + GLuint vbo; + + GLuint screenmat_loc; + GLuint scale_loc; + GLuint offset_loc; + GLuint alpha_loc; + }; + +} diff --git a/clipper/clipper.cpp b/clipper/clipper.cpp new file mode 100644 index 000000000..09657560f --- /dev/null +++ b/clipper/clipper.cpp @@ -0,0 +1,4622 @@ +/******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 6.4.0 * +* Date : 2 July 2015 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2015 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +* Attributions: * +* The code in this library is an extension of Bala Vatti's clipping algorithm: * +* "A generic solution to polygon clipping" * +* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * +* http://portal.acm.org/citation.cfm?id=129906 * +* * +* Computer graphics and geometric modeling: implementation and algorithms * +* By Max K. Agoston * +* Springer; 1 edition (January 4, 2005) * +* http://books.google.com/books?q=vatti+clipping+agoston * +* * +* See also: * +* "Polygon Offsetting by Computing Winding Numbers" * +* Paper no. DETC2005-85513 pp. 565-575 * +* ASME 2005 International Design Engineering Technical Conferences * +* and Computers and Information in Engineering Conference (IDETC/CIE2005) * +* September 24-28, 2005 , Long Beach, California, USA * +* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * +* * +*******************************************************************************/ + +/******************************************************************************* +* * +* This is a translation of the Delphi Clipper library and the naming style * +* used has retained a Delphi flavour. * +* * +*******************************************************************************/ + +#include "clipper.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ClipperLib { + +static double const pi = 3.141592653589793238; +static double const two_pi = pi *2; +static double const def_arc_tolerance = 0.25; + +enum Direction { dRightToLeft, dLeftToRight }; + +static int const Unassigned = -1; //edge not currently 'owning' a solution +static int const Skip = -2; //edge that would otherwise close a path + +#define HORIZONTAL (-1.0E+40) +#define TOLERANCE (1.0e-20) +#define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE)) + +struct TEdge { + IntPoint Bot; + IntPoint Curr; //current (updated for every new scanbeam) + IntPoint Top; + double Dx; + PolyType PolyTyp; + EdgeSide Side; //side only refers to current side of solution poly + int WindDelta; //1 or -1 depending on winding direction + int WindCnt; + int WindCnt2; //winding count of the opposite polytype + int OutIdx; + TEdge *Next; + TEdge *Prev; + TEdge *NextInLML; + TEdge *NextInAEL; + TEdge *PrevInAEL; + TEdge *NextInSEL; + TEdge *PrevInSEL; +}; + +struct IntersectNode { + TEdge *Edge1; + TEdge *Edge2; + IntPoint Pt; +}; + +struct LocalMinimum { + cInt Y; + TEdge *LeftBound; + TEdge *RightBound; +}; + +struct OutPt; + +//OutRec: contains a path in the clipping solution. Edges in the AEL will +//carry a pointer to an OutRec when they are part of the clipping solution. +struct OutRec { + int Idx; + bool IsHole; + bool IsOpen; + OutRec *FirstLeft; //see comments in clipper.pas + PolyNode *PolyNd; + OutPt *Pts; + OutPt *BottomPt; +}; + +struct OutPt { + int Idx; + IntPoint Pt; + OutPt *Next; + OutPt *Prev; +}; + +struct Join { + OutPt *OutPt1; + OutPt *OutPt2; + IntPoint OffPt; +}; + +struct LocMinSorter +{ + inline bool operator()(const LocalMinimum& locMin1, const LocalMinimum& locMin2) + { + return locMin2.Y < locMin1.Y; + } +}; + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +inline cInt Round(double val) +{ + if ((val < 0)) return static_cast(val - 0.5); + else return static_cast(val + 0.5); +} +//------------------------------------------------------------------------------ + +inline cInt Abs(cInt val) +{ + return val < 0 ? -val : val; +} + +//------------------------------------------------------------------------------ +// PolyTree methods ... +//------------------------------------------------------------------------------ + +void PolyTree::Clear() +{ + for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i) + delete AllNodes[i]; + AllNodes.resize(0); + Childs.resize(0); +} +//------------------------------------------------------------------------------ + +PolyNode* PolyTree::GetFirst() const +{ + if (!Childs.empty()) + return Childs[0]; + else + return 0; +} +//------------------------------------------------------------------------------ + +int PolyTree::Total() const +{ + int result = (int)AllNodes.size(); + //with negative offsets, ignore the hidden outer polygon ... + if (result > 0 && Childs[0] != AllNodes[0]) result--; + return result; +} + +//------------------------------------------------------------------------------ +// PolyNode methods ... +//------------------------------------------------------------------------------ + +PolyNode::PolyNode(): Childs(), Parent(0), Index(0), m_IsOpen(false) +{ +} +//------------------------------------------------------------------------------ + +int PolyNode::ChildCount() const +{ + return (int)Childs.size(); +} +//------------------------------------------------------------------------------ + +void PolyNode::AddChild(PolyNode& child) +{ + unsigned cnt = (unsigned)Childs.size(); + Childs.push_back(&child); + child.Parent = this; + child.Index = cnt; +} +//------------------------------------------------------------------------------ + +PolyNode* PolyNode::GetNext() const +{ + if (!Childs.empty()) + return Childs[0]; + else + return GetNextSiblingUp(); +} +//------------------------------------------------------------------------------ + +PolyNode* PolyNode::GetNextSiblingUp() const +{ + if (!Parent) //protects against PolyTree.GetNextSiblingUp() + return 0; + else if (Index == Parent->Childs.size() - 1) + return Parent->GetNextSiblingUp(); + else + return Parent->Childs[Index + 1]; +} +//------------------------------------------------------------------------------ + +bool PolyNode::IsHole() const +{ + bool result = true; + PolyNode* node = Parent; + while (node) + { + result = !result; + node = node->Parent; + } + return result; +} +//------------------------------------------------------------------------------ + +bool PolyNode::IsOpen() const +{ + return m_IsOpen; +} +//------------------------------------------------------------------------------ + +#ifndef use_int32 + +//------------------------------------------------------------------------------ +// Int128 class (enables safe math on signed 64bit integers) +// eg Int128 val1((long64)9223372036854775807); //ie 2^63 -1 +// Int128 val2((long64)9223372036854775807); +// Int128 val3 = val1 * val2; +// val3.AsString => "85070591730234615847396907784232501249" (8.5e+37) +//------------------------------------------------------------------------------ + +class Int128 +{ + public: + ulong64 lo; + long64 hi; + + Int128(long64 _lo = 0) + { + lo = (ulong64)_lo; + if (_lo < 0) hi = -1; else hi = 0; + } + + + Int128(const Int128 &val): lo(val.lo), hi(val.hi){} + + Int128(const long64& _hi, const ulong64& _lo): lo(_lo), hi(_hi){} + + Int128& operator = (const long64 &val) + { + lo = (ulong64)val; + if (val < 0) hi = -1; else hi = 0; + return *this; + } + + bool operator == (const Int128 &val) const + {return (hi == val.hi && lo == val.lo);} + + bool operator != (const Int128 &val) const + { return !(*this == val);} + + bool operator > (const Int128 &val) const + { + if (hi != val.hi) + return hi > val.hi; + else + return lo > val.lo; + } + + bool operator < (const Int128 &val) const + { + if (hi != val.hi) + return hi < val.hi; + else + return lo < val.lo; + } + + bool operator >= (const Int128 &val) const + { return !(*this < val);} + + bool operator <= (const Int128 &val) const + { return !(*this > val);} + + Int128& operator += (const Int128 &rhs) + { + hi += rhs.hi; + lo += rhs.lo; + if (lo < rhs.lo) hi++; + return *this; + } + + Int128 operator + (const Int128 &rhs) const + { + Int128 result(*this); + result+= rhs; + return result; + } + + Int128& operator -= (const Int128 &rhs) + { + *this += -rhs; + return *this; + } + + Int128 operator - (const Int128 &rhs) const + { + Int128 result(*this); + result -= rhs; + return result; + } + + Int128 operator-() const //unary negation + { + if (lo == 0) + return Int128(-hi, 0); + else + return Int128(~hi, ~lo + 1); + } + + operator double() const + { + const double shift64 = 18446744073709551616.0; //2^64 + if (hi < 0) + { + if (lo == 0) return (double)hi * shift64; + else return -(double)(~lo + ~hi * shift64); + } + else + return (double)(lo + hi * shift64); + } + +}; +//------------------------------------------------------------------------------ + +Int128 Int128Mul (long64 lhs, long64 rhs) +{ + bool negate = (lhs < 0) != (rhs < 0); + + if (lhs < 0) lhs = -lhs; + ulong64 int1Hi = ulong64(lhs) >> 32; + ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF); + + if (rhs < 0) rhs = -rhs; + ulong64 int2Hi = ulong64(rhs) >> 32; + ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF); + + //nb: see comments in clipper.pas + ulong64 a = int1Hi * int2Hi; + ulong64 b = int1Lo * int2Lo; + ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi; + + Int128 tmp; + tmp.hi = long64(a + (c >> 32)); + tmp.lo = long64(c << 32); + tmp.lo += long64(b); + if (tmp.lo < b) tmp.hi++; + if (negate) tmp = -tmp; + return tmp; +}; +#endif + +//------------------------------------------------------------------------------ +// Miscellaneous global functions +//------------------------------------------------------------------------------ + +bool Orientation(const Path &poly) +{ + return Area(poly) >= 0; +} +//------------------------------------------------------------------------------ + +double Area(const Path &poly) +{ + int size = (int)poly.size(); + if (size < 3) return 0; + + double a = 0; + for (int i = 0, j = size -1; i < size; ++i) + { + a += ((double)poly[j].X + poly[i].X) * ((double)poly[j].Y - poly[i].Y); + j = i; + } + return -a * 0.5; +} +//------------------------------------------------------------------------------ + +double Area(const OutPt *op) +{ + const OutPt *startOp = op; + if (!op) return 0; + double a = 0; + do { + a += (double)(op->Prev->Pt.X + op->Pt.X) * (double)(op->Prev->Pt.Y - op->Pt.Y); + op = op->Next; + } while (op != startOp); + return a * 0.5; +} +//------------------------------------------------------------------------------ + +double Area(const OutRec &outRec) +{ + return Area(outRec.Pts); +} +//------------------------------------------------------------------------------ + +bool PointIsVertex(const IntPoint &Pt, OutPt *pp) +{ + OutPt *pp2 = pp; + do + { + if (pp2->Pt == Pt) return true; + pp2 = pp2->Next; + } + while (pp2 != pp); + return false; +} +//------------------------------------------------------------------------------ + +//See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos +//http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf +int PointInPolygon(const IntPoint &pt, const Path &path) +{ + //returns 0 if false, +1 if true, -1 if pt ON polygon boundary + int result = 0; + size_t cnt = path.size(); + if (cnt < 3) return 0; + IntPoint ip = path[0]; + for(size_t i = 1; i <= cnt; ++i) + { + IntPoint ipNext = (i == cnt ? path[0] : path[i]); + if (ipNext.Y == pt.Y) + { + if ((ipNext.X == pt.X) || (ip.Y == pt.Y && + ((ipNext.X > pt.X) == (ip.X < pt.X)))) return -1; + } + if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y)) + { + if (ip.X >= pt.X) + { + if (ipNext.X > pt.X) result = 1 - result; + else + { + double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - + (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); + if (!d) return -1; + if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; + } + } else + { + if (ipNext.X > pt.X) + { + double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - + (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); + if (!d) return -1; + if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; + } + } + } + ip = ipNext; + } + return result; +} +//------------------------------------------------------------------------------ + +int PointInPolygon (const IntPoint &pt, OutPt *op) +{ + //returns 0 if false, +1 if true, -1 if pt ON polygon boundary + int result = 0; + OutPt* startOp = op; + for(;;) + { + if (op->Next->Pt.Y == pt.Y) + { + if ((op->Next->Pt.X == pt.X) || (op->Pt.Y == pt.Y && + ((op->Next->Pt.X > pt.X) == (op->Pt.X < pt.X)))) return -1; + } + if ((op->Pt.Y < pt.Y) != (op->Next->Pt.Y < pt.Y)) + { + if (op->Pt.X >= pt.X) + { + if (op->Next->Pt.X > pt.X) result = 1 - result; + else + { + double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - + (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); + if (!d) return -1; + if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; + } + } else + { + if (op->Next->Pt.X > pt.X) + { + double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - + (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); + if (!d) return -1; + if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; + } + } + } + op = op->Next; + if (startOp == op) break; + } + return result; +} +//------------------------------------------------------------------------------ + +bool Poly2ContainsPoly1(OutPt *OutPt1, OutPt *OutPt2) +{ + OutPt* op = OutPt1; + do + { + //nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon + int res = PointInPolygon(op->Pt, OutPt2); + if (res >= 0) return res > 0; + op = op->Next; + } + while (op != OutPt1); + return true; +} +//---------------------------------------------------------------------- + +bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) +{ +#ifndef use_int32 + if (UseFullInt64Range) + return Int128Mul(e1.Top.Y - e1.Bot.Y, e2.Top.X - e2.Bot.X) == + Int128Mul(e1.Top.X - e1.Bot.X, e2.Top.Y - e2.Bot.Y); + else +#endif + return (e1.Top.Y - e1.Bot.Y) * (e2.Top.X - e2.Bot.X) == + (e1.Top.X - e1.Bot.X) * (e2.Top.Y - e2.Bot.Y); +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, + const IntPoint pt3, bool UseFullInt64Range) +{ +#ifndef use_int32 + if (UseFullInt64Range) + return Int128Mul(pt1.Y-pt2.Y, pt2.X-pt3.X) == Int128Mul(pt1.X-pt2.X, pt2.Y-pt3.Y); + else +#endif + return (pt1.Y-pt2.Y)*(pt2.X-pt3.X) == (pt1.X-pt2.X)*(pt2.Y-pt3.Y); +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, + const IntPoint pt3, const IntPoint pt4, bool UseFullInt64Range) +{ +#ifndef use_int32 + if (UseFullInt64Range) + return Int128Mul(pt1.Y-pt2.Y, pt3.X-pt4.X) == Int128Mul(pt1.X-pt2.X, pt3.Y-pt4.Y); + else +#endif + return (pt1.Y-pt2.Y)*(pt3.X-pt4.X) == (pt1.X-pt2.X)*(pt3.Y-pt4.Y); +} +//------------------------------------------------------------------------------ + +inline bool IsHorizontal(TEdge &e) +{ + return e.Dx == HORIZONTAL; +} +//------------------------------------------------------------------------------ + +inline double GetDx(const IntPoint pt1, const IntPoint pt2) +{ + return (pt1.Y == pt2.Y) ? + HORIZONTAL : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); +} +//--------------------------------------------------------------------------- + +inline void SetDx(TEdge &e) +{ + cInt dy = (e.Top.Y - e.Bot.Y); + if (dy == 0) e.Dx = HORIZONTAL; + else e.Dx = (double)(e.Top.X - e.Bot.X) / dy; +} +//--------------------------------------------------------------------------- + +inline void SwapSides(TEdge &Edge1, TEdge &Edge2) +{ + EdgeSide Side = Edge1.Side; + Edge1.Side = Edge2.Side; + Edge2.Side = Side; +} +//------------------------------------------------------------------------------ + +inline void SwapPolyIndexes(TEdge &Edge1, TEdge &Edge2) +{ + int OutIdx = Edge1.OutIdx; + Edge1.OutIdx = Edge2.OutIdx; + Edge2.OutIdx = OutIdx; +} +//------------------------------------------------------------------------------ + +inline cInt TopX(TEdge &edge, const cInt currentY) +{ + return ( currentY == edge.Top.Y ) ? + edge.Top.X : edge.Bot.X + Round(edge.Dx *(currentY - edge.Bot.Y)); +} +//------------------------------------------------------------------------------ + +void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip) +{ +#ifdef use_xyz + ip.Z = 0; +#endif + + double b1, b2; + if (Edge1.Dx == Edge2.Dx) + { + ip.Y = Edge1.Curr.Y; + ip.X = TopX(Edge1, ip.Y); + return; + } + else if (Edge1.Dx == 0) + { + ip.X = Edge1.Bot.X; + if (IsHorizontal(Edge2)) + ip.Y = Edge2.Bot.Y; + else + { + b2 = Edge2.Bot.Y - (Edge2.Bot.X / Edge2.Dx); + ip.Y = Round(ip.X / Edge2.Dx + b2); + } + } + else if (Edge2.Dx == 0) + { + ip.X = Edge2.Bot.X; + if (IsHorizontal(Edge1)) + ip.Y = Edge1.Bot.Y; + else + { + b1 = Edge1.Bot.Y - (Edge1.Bot.X / Edge1.Dx); + ip.Y = Round(ip.X / Edge1.Dx + b1); + } + } + else + { + b1 = Edge1.Bot.X - Edge1.Bot.Y * Edge1.Dx; + b2 = Edge2.Bot.X - Edge2.Bot.Y * Edge2.Dx; + double q = (b2-b1) / (Edge1.Dx - Edge2.Dx); + ip.Y = Round(q); + if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) + ip.X = Round(Edge1.Dx * q + b1); + else + ip.X = Round(Edge2.Dx * q + b2); + } + + if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y) + { + if (Edge1.Top.Y > Edge2.Top.Y) + ip.Y = Edge1.Top.Y; + else + ip.Y = Edge2.Top.Y; + if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) + ip.X = TopX(Edge1, ip.Y); + else + ip.X = TopX(Edge2, ip.Y); + } + //finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ... + if (ip.Y > Edge1.Curr.Y) + { + ip.Y = Edge1.Curr.Y; + //use the more vertical edge to derive X ... + if (std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx)) + ip.X = TopX(Edge2, ip.Y); else + ip.X = TopX(Edge1, ip.Y); + } +} +//------------------------------------------------------------------------------ + +void ReversePolyPtLinks(OutPt *pp) +{ + if (!pp) return; + OutPt *pp1, *pp2; + pp1 = pp; + do { + pp2 = pp1->Next; + pp1->Next = pp1->Prev; + pp1->Prev = pp2; + pp1 = pp2; + } while( pp1 != pp ); +} +//------------------------------------------------------------------------------ + +void DisposeOutPts(OutPt*& pp) +{ + if (pp == 0) return; + pp->Prev->Next = 0; + while( pp ) + { + OutPt *tmpPp = pp; + pp = pp->Next; + delete tmpPp; + } +} +//------------------------------------------------------------------------------ + +inline void InitEdge(TEdge* e, TEdge* eNext, TEdge* ePrev, const IntPoint& Pt) +{ + std::memset(e, 0, sizeof(TEdge)); + e->Next = eNext; + e->Prev = ePrev; + e->Curr = Pt; + e->OutIdx = Unassigned; +} +//------------------------------------------------------------------------------ + +void InitEdge2(TEdge& e, PolyType Pt) +{ + if (e.Curr.Y >= e.Next->Curr.Y) + { + e.Bot = e.Curr; + e.Top = e.Next->Curr; + } else + { + e.Top = e.Curr; + e.Bot = e.Next->Curr; + } + SetDx(e); + e.PolyTyp = Pt; +} +//------------------------------------------------------------------------------ + +TEdge* RemoveEdge(TEdge* e) +{ + //removes e from double_linked_list (but without removing from memory) + e->Prev->Next = e->Next; + e->Next->Prev = e->Prev; + TEdge* result = e->Next; + e->Prev = 0; //flag as removed (see ClipperBase.Clear) + return result; +} +//------------------------------------------------------------------------------ + +inline void ReverseHorizontal(TEdge &e) +{ + //swap horizontal edges' Top and Bottom x's so they follow the natural + //progression of the bounds - ie so their xbots will align with the + //adjoining lower edge. [Helpful in the ProcessHorizontal() method.] + std::swap(e.Top.X, e.Bot.X); +#ifdef use_xyz + std::swap(e.Top.Z, e.Bot.Z); +#endif +} +//------------------------------------------------------------------------------ + +void SwapPoints(IntPoint &pt1, IntPoint &pt2) +{ + IntPoint tmp = pt1; + pt1 = pt2; + pt2 = tmp; +} +//------------------------------------------------------------------------------ + +bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, + IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) +{ + //precondition: segments are Collinear. + if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) + { + if (pt1a.X > pt1b.X) SwapPoints(pt1a, pt1b); + if (pt2a.X > pt2b.X) SwapPoints(pt2a, pt2b); + if (pt1a.X > pt2a.X) pt1 = pt1a; else pt1 = pt2a; + if (pt1b.X < pt2b.X) pt2 = pt1b; else pt2 = pt2b; + return pt1.X < pt2.X; + } else + { + if (pt1a.Y < pt1b.Y) SwapPoints(pt1a, pt1b); + if (pt2a.Y < pt2b.Y) SwapPoints(pt2a, pt2b); + if (pt1a.Y < pt2a.Y) pt1 = pt1a; else pt1 = pt2a; + if (pt1b.Y > pt2b.Y) pt2 = pt1b; else pt2 = pt2b; + return pt1.Y > pt2.Y; + } +} +//------------------------------------------------------------------------------ + +bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2) +{ + OutPt *p = btmPt1->Prev; + while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Prev; + double dx1p = std::fabs(GetDx(btmPt1->Pt, p->Pt)); + p = btmPt1->Next; + while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Next; + double dx1n = std::fabs(GetDx(btmPt1->Pt, p->Pt)); + + p = btmPt2->Prev; + while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Prev; + double dx2p = std::fabs(GetDx(btmPt2->Pt, p->Pt)); + p = btmPt2->Next; + while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Next; + double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt)); + + if (std::max(dx1p, dx1n) == std::max(dx2p, dx2n) && + std::min(dx1p, dx1n) == std::min(dx2p, dx2n)) + return Area(btmPt1) > 0; //if otherwise identical use orientation + else + return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); +} +//------------------------------------------------------------------------------ + +OutPt* GetBottomPt(OutPt *pp) +{ + OutPt* dups = 0; + OutPt* p = pp->Next; + while (p != pp) + { + if (p->Pt.Y > pp->Pt.Y) + { + pp = p; + dups = 0; + } + else if (p->Pt.Y == pp->Pt.Y && p->Pt.X <= pp->Pt.X) + { + if (p->Pt.X < pp->Pt.X) + { + dups = 0; + pp = p; + } else + { + if (p->Next != pp && p->Prev != pp) dups = p; + } + } + p = p->Next; + } + if (dups) + { + //there appears to be at least 2 vertices at BottomPt so ... + while (dups != p) + { + if (!FirstIsBottomPt(p, dups)) pp = dups; + dups = dups->Next; + while (dups->Pt != pp->Pt) dups = dups->Next; + } + } + return pp; +} +//------------------------------------------------------------------------------ + +bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1, + const IntPoint pt2, const IntPoint pt3) +{ + if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) + return false; + else if (pt1.X != pt3.X) + return (pt2.X > pt1.X) == (pt2.X < pt3.X); + else + return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y); +} +//------------------------------------------------------------------------------ + +bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b) +{ + if (seg1a > seg1b) std::swap(seg1a, seg1b); + if (seg2a > seg2b) std::swap(seg2a, seg2b); + return (seg1a < seg2b) && (seg2a < seg1b); +} + +//------------------------------------------------------------------------------ +// ClipperBase class methods ... +//------------------------------------------------------------------------------ + +ClipperBase::ClipperBase() //constructor +{ + m_CurrentLM = m_MinimaList.begin(); //begin() == end() here + m_UseFullRange = false; +} +//------------------------------------------------------------------------------ + +ClipperBase::~ClipperBase() //destructor +{ + Clear(); +} +//------------------------------------------------------------------------------ + +void RangeTest(const IntPoint& Pt, bool& useFullRange) +{ + if (useFullRange) + { + if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange) + throw clipperException("Coordinate outside allowed range"); + } + else if (Pt.X > loRange|| Pt.Y > loRange || -Pt.X > loRange || -Pt.Y > loRange) + { + useFullRange = true; + RangeTest(Pt, useFullRange); + } +} +//------------------------------------------------------------------------------ + +TEdge* FindNextLocMin(TEdge* E) +{ + for (;;) + { + while (E->Bot != E->Prev->Bot || E->Curr == E->Top) E = E->Next; + if (!IsHorizontal(*E) && !IsHorizontal(*E->Prev)) break; + while (IsHorizontal(*E->Prev)) E = E->Prev; + TEdge* E2 = E; + while (IsHorizontal(*E)) E = E->Next; + if (E->Top.Y == E->Prev->Bot.Y) continue; //ie just an intermediate horz. + if (E2->Prev->Bot.X < E->Bot.X) E = E2; + break; + } + return E; +} +//------------------------------------------------------------------------------ + +TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward) +{ + TEdge *Result = E; + TEdge *Horz = 0; + + if (E->OutIdx == Skip) + { + //if edges still remain in the current bound beyond the skip edge then + //create another LocMin and call ProcessBound once more + if (NextIsForward) + { + while (E->Top.Y == E->Next->Bot.Y) E = E->Next; + //don't include top horizontals when parsing a bound a second time, + //they will be contained in the opposite bound ... + while (E != Result && IsHorizontal(*E)) E = E->Prev; + } + else + { + while (E->Top.Y == E->Prev->Bot.Y) E = E->Prev; + while (E != Result && IsHorizontal(*E)) E = E->Next; + } + + if (E == Result) + { + if (NextIsForward) Result = E->Next; + else Result = E->Prev; + } + else + { + //there are more edges in the bound beyond result starting with E + if (NextIsForward) + E = Result->Next; + else + E = Result->Prev; + MinimaList::value_type locMin; + locMin.Y = E->Bot.Y; + locMin.LeftBound = 0; + locMin.RightBound = E; + E->WindDelta = 0; + Result = ProcessBound(E, NextIsForward); + m_MinimaList.push_back(locMin); + } + return Result; + } + + TEdge *EStart; + + if (IsHorizontal(*E)) + { + //We need to be careful with open paths because this may not be a + //true local minima (ie E may be following a skip edge). + //Also, consecutive horz. edges may start heading left before going right. + if (NextIsForward) + EStart = E->Prev; + else + EStart = E->Next; + if (IsHorizontal(*EStart)) //ie an adjoining horizontal skip edge + { + if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X) + ReverseHorizontal(*E); + } + else if (EStart->Bot.X != E->Bot.X) + ReverseHorizontal(*E); + } + + EStart = E; + if (NextIsForward) + { + while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip) + Result = Result->Next; + if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) + { + //nb: at the top of a bound, horizontals are added to the bound + //only when the preceding edge attaches to the horizontal's left vertex + //unless a Skip edge is encountered when that becomes the top divide + Horz = Result; + while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev; + if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev; + } + while (E != Result) + { + E->NextInLML = E->Next; + if (IsHorizontal(*E) && E != EStart && + E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); + E = E->Next; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) + ReverseHorizontal(*E); + Result = Result->Next; //move to the edge just beyond current bound + } else + { + while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip) + Result = Result->Prev; + if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) + { + Horz = Result; + while (IsHorizontal(*Horz->Next)) Horz = Horz->Next; + if (Horz->Next->Top.X == Result->Prev->Top.X || + Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next; + } + + while (E != Result) + { + E->NextInLML = E->Prev; + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + E = E->Prev; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + Result = Result->Prev; //move to the edge just beyond current bound + } + + return Result; +} +//------------------------------------------------------------------------------ + +bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) +{ +#ifdef use_lines + if (!Closed && PolyTyp == ptClip) + throw clipperException("AddPath: Open paths must be subject."); +#else + if (!Closed) + throw clipperException("AddPath: Open paths have been disabled."); +#endif + + int highI = (int)pg.size() -1; + if (Closed) while (highI > 0 && (pg[highI] == pg[0])) --highI; + while (highI > 0 && (pg[highI] == pg[highI -1])) --highI; + if ((Closed && highI < 2) || (!Closed && highI < 1)) return false; + + //create a new edge array ... + TEdge *edges = new TEdge [highI +1]; + + bool IsFlat = true; + //1. Basic (first) edge initialization ... + try + { + edges[1].Curr = pg[1]; + RangeTest(pg[0], m_UseFullRange); + RangeTest(pg[highI], m_UseFullRange); + InitEdge(&edges[0], &edges[1], &edges[highI], pg[0]); + InitEdge(&edges[highI], &edges[0], &edges[highI-1], pg[highI]); + for (int i = highI - 1; i >= 1; --i) + { + RangeTest(pg[i], m_UseFullRange); + InitEdge(&edges[i], &edges[i+1], &edges[i-1], pg[i]); + } + } + catch(...) + { + delete [] edges; + throw; //range test fails + } + TEdge *eStart = &edges[0]; + + //2. Remove duplicate vertices, and (when closed) collinear edges ... + TEdge *E = eStart, *eLoopStop = eStart; + for (;;) + { + //nb: allows matching start and end points when not Closed ... + if (E->Curr == E->Next->Curr && (Closed || E->Next != eStart)) + { + if (E == E->Next) break; + if (E == eStart) eStart = E->Next; + E = RemoveEdge(E); + eLoopStop = E; + continue; + } + if (E->Prev == E->Next) + break; //only two vertices + else if (Closed && + SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr, m_UseFullRange) && + (!m_PreserveCollinear || + !Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr))) + { + //Collinear edges are allowed for open paths but in closed paths + //the default is to merge adjacent collinear edges into a single edge. + //However, if the PreserveCollinear property is enabled, only overlapping + //collinear edges (ie spikes) will be removed from closed paths. + if (E == eStart) eStart = E->Next; + E = RemoveEdge(E); + E = E->Prev; + eLoopStop = E; + continue; + } + E = E->Next; + if ((E == eLoopStop) || (!Closed && E->Next == eStart)) break; + } + + if ((!Closed && (E == E->Next)) || (Closed && (E->Prev == E->Next))) + { + delete [] edges; + return false; + } + + if (!Closed) + { + m_HasOpenPaths = true; + eStart->Prev->OutIdx = Skip; + } + + //3. Do second stage of edge initialization ... + E = eStart; + do + { + InitEdge2(*E, PolyTyp); + E = E->Next; + if (IsFlat && E->Curr.Y != eStart->Curr.Y) IsFlat = false; + } + while (E != eStart); + + //4. Finally, add edge bounds to LocalMinima list ... + + //Totally flat paths must be handled differently when adding them + //to LocalMinima list to avoid endless loops etc ... + if (IsFlat) + { + if (Closed) + { + delete [] edges; + return false; + } + E->Prev->OutIdx = Skip; + MinimaList::value_type locMin; + locMin.Y = E->Bot.Y; + locMin.LeftBound = 0; + locMin.RightBound = E; + locMin.RightBound->Side = esRight; + locMin.RightBound->WindDelta = 0; + for (;;) + { + if (E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); + if (E->Next->OutIdx == Skip) break; + E->NextInLML = E->Next; + E = E->Next; + } + m_MinimaList.push_back(locMin); + m_edges.push_back(edges); + return true; + } + + m_edges.push_back(edges); + bool leftBoundIsForward; + TEdge* EMin = 0; + + //workaround to avoid an endless loop in the while loop below when + //open paths have matching start and end points ... + if (E->Prev->Bot == E->Prev->Top) E = E->Next; + + for (;;) + { + E = FindNextLocMin(E); + if (E == EMin) break; + else if (!EMin) EMin = E; + + //E and E.Prev now share a local minima (left aligned if horizontal). + //Compare their slopes to find which starts which bound ... + MinimaList::value_type locMin; + locMin.Y = E->Bot.Y; + if (E->Dx < E->Prev->Dx) + { + locMin.LeftBound = E->Prev; + locMin.RightBound = E; + leftBoundIsForward = false; //Q.nextInLML = Q.prev + } else + { + locMin.LeftBound = E; + locMin.RightBound = E->Prev; + leftBoundIsForward = true; //Q.nextInLML = Q.next + } + + if (!Closed) locMin.LeftBound->WindDelta = 0; + else if (locMin.LeftBound->Next == locMin.RightBound) + locMin.LeftBound->WindDelta = -1; + else locMin.LeftBound->WindDelta = 1; + locMin.RightBound->WindDelta = -locMin.LeftBound->WindDelta; + + E = ProcessBound(locMin.LeftBound, leftBoundIsForward); + if (E->OutIdx == Skip) E = ProcessBound(E, leftBoundIsForward); + + TEdge* E2 = ProcessBound(locMin.RightBound, !leftBoundIsForward); + if (E2->OutIdx == Skip) E2 = ProcessBound(E2, !leftBoundIsForward); + + if (locMin.LeftBound->OutIdx == Skip) + locMin.LeftBound = 0; + else if (locMin.RightBound->OutIdx == Skip) + locMin.RightBound = 0; + m_MinimaList.push_back(locMin); + if (!leftBoundIsForward) E = E2; + } + return true; +} +//------------------------------------------------------------------------------ + +bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed) +{ + bool result = false; + for (Paths::size_type i = 0; i < ppg.size(); ++i) + if (AddPath(ppg[i], PolyTyp, Closed)) result = true; + return result; +} +//------------------------------------------------------------------------------ + +void ClipperBase::Clear() +{ + DisposeLocalMinimaList(); + for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) + { + TEdge* edges = m_edges[i]; + delete [] edges; + } + m_edges.clear(); + m_UseFullRange = false; + m_HasOpenPaths = false; +} +//------------------------------------------------------------------------------ + +void ClipperBase::Reset() +{ + m_CurrentLM = m_MinimaList.begin(); + if (m_CurrentLM == m_MinimaList.end()) return; //ie nothing to process + std::sort(m_MinimaList.begin(), m_MinimaList.end(), LocMinSorter()); + + m_Scanbeam = ScanbeamList(); //clears/resets priority_queue + //reset all edges ... + for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end(); ++lm) + { + InsertScanbeam(lm->Y); + TEdge* e = lm->LeftBound; + if (e) + { + e->Curr = e->Bot; + e->Side = esLeft; + e->OutIdx = Unassigned; + } + + e = lm->RightBound; + if (e) + { + e->Curr = e->Bot; + e->Side = esRight; + e->OutIdx = Unassigned; + } + } + m_ActiveEdges = 0; + m_CurrentLM = m_MinimaList.begin(); +} +//------------------------------------------------------------------------------ + +void ClipperBase::DisposeLocalMinimaList() +{ + m_MinimaList.clear(); + m_CurrentLM = m_MinimaList.begin(); +} +//------------------------------------------------------------------------------ + +bool ClipperBase::PopLocalMinima(cInt Y, const LocalMinimum *&locMin) +{ + if (m_CurrentLM == m_MinimaList.end() || (*m_CurrentLM).Y != Y) return false; + locMin = &(*m_CurrentLM); + ++m_CurrentLM; + return true; +} +//------------------------------------------------------------------------------ + +IntRect ClipperBase::GetBounds() +{ + IntRect result; + MinimaList::iterator lm = m_MinimaList.begin(); + if (lm == m_MinimaList.end()) + { + result.left = result.top = result.right = result.bottom = 0; + return result; + } + result.left = lm->LeftBound->Bot.X; + result.top = lm->LeftBound->Bot.Y; + result.right = lm->LeftBound->Bot.X; + result.bottom = lm->LeftBound->Bot.Y; + while (lm != m_MinimaList.end()) + { + //todo - needs fixing for open paths + result.bottom = std::max(result.bottom, lm->LeftBound->Bot.Y); + TEdge* e = lm->LeftBound; + for (;;) { + TEdge* bottomE = e; + while (e->NextInLML) + { + if (e->Bot.X < result.left) result.left = e->Bot.X; + if (e->Bot.X > result.right) result.right = e->Bot.X; + e = e->NextInLML; + } + result.left = std::min(result.left, e->Bot.X); + result.right = std::max(result.right, e->Bot.X); + result.left = std::min(result.left, e->Top.X); + result.right = std::max(result.right, e->Top.X); + result.top = std::min(result.top, e->Top.Y); + if (bottomE == lm->LeftBound) e = lm->RightBound; + else break; + } + ++lm; + } + return result; +} +//------------------------------------------------------------------------------ + +void ClipperBase::InsertScanbeam(const cInt Y) +{ + m_Scanbeam.push(Y); +} +//------------------------------------------------------------------------------ + +bool ClipperBase::PopScanbeam(cInt &Y) +{ + if (m_Scanbeam.empty()) return false; + Y = m_Scanbeam.top(); + m_Scanbeam.pop(); + while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) { m_Scanbeam.pop(); } // Pop duplicates. + return true; +} +//------------------------------------------------------------------------------ + +void ClipperBase::DisposeAllOutRecs(){ + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + DisposeOutRec(i); + m_PolyOuts.clear(); +} +//------------------------------------------------------------------------------ + +void ClipperBase::DisposeOutRec(PolyOutList::size_type index) +{ + OutRec *outRec = m_PolyOuts[index]; + if (outRec->Pts) DisposeOutPts(outRec->Pts); + delete outRec; + m_PolyOuts[index] = 0; +} +//------------------------------------------------------------------------------ + +void ClipperBase::DeleteFromAEL(TEdge *e) +{ + TEdge* AelPrev = e->PrevInAEL; + TEdge* AelNext = e->NextInAEL; + if (!AelPrev && !AelNext && (e != m_ActiveEdges)) return; //already deleted + if (AelPrev) AelPrev->NextInAEL = AelNext; + else m_ActiveEdges = AelNext; + if (AelNext) AelNext->PrevInAEL = AelPrev; + e->NextInAEL = 0; + e->PrevInAEL = 0; +} +//------------------------------------------------------------------------------ + +OutRec* ClipperBase::CreateOutRec() +{ + OutRec* result = new OutRec; + result->IsHole = false; + result->IsOpen = false; + result->FirstLeft = 0; + result->Pts = 0; + result->BottomPt = 0; + result->PolyNd = 0; + m_PolyOuts.push_back(result); + result->Idx = (int)m_PolyOuts.size() - 1; + return result; +} +//------------------------------------------------------------------------------ + +void ClipperBase::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2) +{ + //check that one or other edge hasn't already been removed from AEL ... + if (Edge1->NextInAEL == Edge1->PrevInAEL || + Edge2->NextInAEL == Edge2->PrevInAEL) return; + + if (Edge1->NextInAEL == Edge2) + { + TEdge* Next = Edge2->NextInAEL; + if (Next) Next->PrevInAEL = Edge1; + TEdge* Prev = Edge1->PrevInAEL; + if (Prev) Prev->NextInAEL = Edge2; + Edge2->PrevInAEL = Prev; + Edge2->NextInAEL = Edge1; + Edge1->PrevInAEL = Edge2; + Edge1->NextInAEL = Next; + } + else if (Edge2->NextInAEL == Edge1) + { + TEdge* Next = Edge1->NextInAEL; + if (Next) Next->PrevInAEL = Edge2; + TEdge* Prev = Edge2->PrevInAEL; + if (Prev) Prev->NextInAEL = Edge1; + Edge1->PrevInAEL = Prev; + Edge1->NextInAEL = Edge2; + Edge2->PrevInAEL = Edge1; + Edge2->NextInAEL = Next; + } + else + { + TEdge* Next = Edge1->NextInAEL; + TEdge* Prev = Edge1->PrevInAEL; + Edge1->NextInAEL = Edge2->NextInAEL; + if (Edge1->NextInAEL) Edge1->NextInAEL->PrevInAEL = Edge1; + Edge1->PrevInAEL = Edge2->PrevInAEL; + if (Edge1->PrevInAEL) Edge1->PrevInAEL->NextInAEL = Edge1; + Edge2->NextInAEL = Next; + if (Edge2->NextInAEL) Edge2->NextInAEL->PrevInAEL = Edge2; + Edge2->PrevInAEL = Prev; + if (Edge2->PrevInAEL) Edge2->PrevInAEL->NextInAEL = Edge2; + } + + if (!Edge1->PrevInAEL) m_ActiveEdges = Edge1; + else if (!Edge2->PrevInAEL) m_ActiveEdges = Edge2; +} +//------------------------------------------------------------------------------ + +void ClipperBase::UpdateEdgeIntoAEL(TEdge *&e) +{ + if (!e->NextInLML) + throw clipperException("UpdateEdgeIntoAEL: invalid call"); + + e->NextInLML->OutIdx = e->OutIdx; + TEdge* AelPrev = e->PrevInAEL; + TEdge* AelNext = e->NextInAEL; + if (AelPrev) AelPrev->NextInAEL = e->NextInLML; + else m_ActiveEdges = e->NextInLML; + if (AelNext) AelNext->PrevInAEL = e->NextInLML; + e->NextInLML->Side = e->Side; + e->NextInLML->WindDelta = e->WindDelta; + e->NextInLML->WindCnt = e->WindCnt; + e->NextInLML->WindCnt2 = e->WindCnt2; + e = e->NextInLML; + e->Curr = e->Bot; + e->PrevInAEL = AelPrev; + e->NextInAEL = AelNext; + if (!IsHorizontal(*e)) InsertScanbeam(e->Top.Y); +} +//------------------------------------------------------------------------------ + +bool ClipperBase::LocalMinimaPending() +{ + return (m_CurrentLM != m_MinimaList.end()); +} + +//------------------------------------------------------------------------------ +// TClipper methods ... +//------------------------------------------------------------------------------ + +Clipper::Clipper(int initOptions) : ClipperBase() //constructor +{ + m_ExecuteLocked = false; + m_UseFullRange = false; + m_ReverseOutput = ((initOptions & ioReverseSolution) != 0); + m_StrictSimple = ((initOptions & ioStrictlySimple) != 0); + m_PreserveCollinear = ((initOptions & ioPreserveCollinear) != 0); + m_HasOpenPaths = false; +#ifdef use_xyz + m_ZFill = 0; +#endif +} +//------------------------------------------------------------------------------ + +#ifdef use_xyz +void Clipper::ZFillFunction(ZFillCallback zFillFunc) +{ + m_ZFill = zFillFunc; +} +//------------------------------------------------------------------------------ +#endif + +bool Clipper::Execute(ClipType clipType, Paths &solution, PolyFillType fillType) +{ + return Execute(clipType, solution, fillType, fillType); +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, PolyTree &polytree, PolyFillType fillType) +{ + return Execute(clipType, polytree, fillType, fillType); +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, Paths &solution, + PolyFillType subjFillType, PolyFillType clipFillType) +{ + if( m_ExecuteLocked ) return false; + if (m_HasOpenPaths) + throw clipperException("Error: PolyTree struct is needed for open path clipping."); + m_ExecuteLocked = true; + solution.resize(0); + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = false; + bool succeeded = ExecuteInternal(); + if (succeeded) BuildResult(solution); + DisposeAllOutRecs(); + m_ExecuteLocked = false; + return succeeded; +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, PolyTree& polytree, + PolyFillType subjFillType, PolyFillType clipFillType) +{ + if( m_ExecuteLocked ) return false; + m_ExecuteLocked = true; + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = true; + bool succeeded = ExecuteInternal(); + if (succeeded) BuildResult2(polytree); + DisposeAllOutRecs(); + m_ExecuteLocked = false; + return succeeded; +} +//------------------------------------------------------------------------------ + +void Clipper::FixHoleLinkage(OutRec &outrec) +{ + //skip OutRecs that (a) contain outermost polygons or + //(b) already have the correct owner/child linkage ... + if (!outrec.FirstLeft || + (outrec.IsHole != outrec.FirstLeft->IsHole && + outrec.FirstLeft->Pts)) return; + + OutRec* orfl = outrec.FirstLeft; + while (orfl && ((orfl->IsHole == outrec.IsHole) || !orfl->Pts)) + orfl = orfl->FirstLeft; + outrec.FirstLeft = orfl; +} +//------------------------------------------------------------------------------ + +bool Clipper::ExecuteInternal() +{ + bool succeeded = true; + try { + Reset(); + m_Maxima = MaximaList(); + m_SortedEdges = 0; + + succeeded = true; + cInt botY, topY; + if (!PopScanbeam(botY)) return false; + InsertLocalMinimaIntoAEL(botY); + while (PopScanbeam(topY) || LocalMinimaPending()) + { + ProcessHorizontals(); + ClearGhostJoins(); + if (!ProcessIntersections(topY)) + { + succeeded = false; + break; + } + ProcessEdgesAtTopOfScanbeam(topY); + botY = topY; + InsertLocalMinimaIntoAEL(botY); + } + } + catch(...) + { + succeeded = false; + } + + if (succeeded) + { + //fix orientations ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec *outRec = m_PolyOuts[i]; + if (!outRec->Pts || outRec->IsOpen) continue; + if ((outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0)) + ReversePolyPtLinks(outRec->Pts); + } + + if (!m_Joins.empty()) JoinCommonEdges(); + + //unfortunately FixupOutPolygon() must be done after JoinCommonEdges() + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec *outRec = m_PolyOuts[i]; + if (!outRec->Pts) continue; + if (outRec->IsOpen) + FixupOutPolyline(*outRec); + else + FixupOutPolygon(*outRec); + } + + if (m_StrictSimple) DoSimplePolygons(); + } + + ClearJoins(); + ClearGhostJoins(); + return succeeded; +} +//------------------------------------------------------------------------------ + +void Clipper::SetWindingCount(TEdge &edge) +{ + TEdge *e = edge.PrevInAEL; + //find the edge of the same polytype that immediately preceeds 'edge' in AEL + while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0))) e = e->PrevInAEL; + if (!e) + { + if (edge.WindDelta == 0) + { + PolyFillType pft = (edge.PolyTyp == ptSubject ? m_SubjFillType : m_ClipFillType); + edge.WindCnt = (pft == pftNegative ? -1 : 1); + } + else + edge.WindCnt = edge.WindDelta; + edge.WindCnt2 = 0; + e = m_ActiveEdges; //ie get ready to calc WindCnt2 + } + else if (edge.WindDelta == 0 && m_ClipType != ctUnion) + { + edge.WindCnt = 1; + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; //ie get ready to calc WindCnt2 + } + else if (IsEvenOddFillType(edge)) + { + //EvenOdd filling ... + if (edge.WindDelta == 0) + { + //are we inside a subj polygon ... + bool Inside = true; + TEdge *e2 = e->PrevInAEL; + while (e2) + { + if (e2->PolyTyp == e->PolyTyp && e2->WindDelta != 0) + Inside = !Inside; + e2 = e2->PrevInAEL; + } + edge.WindCnt = (Inside ? 0 : 1); + } + else + { + edge.WindCnt = edge.WindDelta; + } + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; //ie get ready to calc WindCnt2 + } + else + { + //nonZero, Positive or Negative filling ... + if (e->WindCnt * e->WindDelta < 0) + { + //prev edge is 'decreasing' WindCount (WC) toward zero + //so we're outside the previous polygon ... + if (Abs(e->WindCnt) > 1) + { + //outside prev poly but still inside another. + //when reversing direction of prev poly use the same WC + if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt; + //otherwise continue to 'decrease' WC ... + else edge.WindCnt = e->WindCnt + edge.WindDelta; + } + else + //now outside all polys of same polytype so set own WC ... + edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); + } else + { + //prev edge is 'increasing' WindCount (WC) away from zero + //so we're inside the previous polygon ... + if (edge.WindDelta == 0) + edge.WindCnt = (e->WindCnt < 0 ? e->WindCnt - 1 : e->WindCnt + 1); + //if wind direction is reversing prev then use same WC + else if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt; + //otherwise add to WC ... + else edge.WindCnt = e->WindCnt + edge.WindDelta; + } + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; //ie get ready to calc WindCnt2 + } + + //update WindCnt2 ... + if (IsEvenOddAltFillType(edge)) + { + //EvenOdd filling ... + while (e != &edge) + { + if (e->WindDelta != 0) + edge.WindCnt2 = (edge.WindCnt2 == 0 ? 1 : 0); + e = e->NextInAEL; + } + } else + { + //nonZero, Positive or Negative filling ... + while ( e != &edge ) + { + edge.WindCnt2 += e->WindDelta; + e = e->NextInAEL; + } + } +} +//------------------------------------------------------------------------------ + +bool Clipper::IsEvenOddFillType(const TEdge& edge) const +{ + if (edge.PolyTyp == ptSubject) + return m_SubjFillType == pftEvenOdd; else + return m_ClipFillType == pftEvenOdd; +} +//------------------------------------------------------------------------------ + +bool Clipper::IsEvenOddAltFillType(const TEdge& edge) const +{ + if (edge.PolyTyp == ptSubject) + return m_ClipFillType == pftEvenOdd; else + return m_SubjFillType == pftEvenOdd; +} +//------------------------------------------------------------------------------ + +bool Clipper::IsContributing(const TEdge& edge) const +{ + PolyFillType pft, pft2; + if (edge.PolyTyp == ptSubject) + { + pft = m_SubjFillType; + pft2 = m_ClipFillType; + } else + { + pft = m_ClipFillType; + pft2 = m_SubjFillType; + } + + switch(pft) + { + case pftEvenOdd: + //return false if a subj line has been flagged as inside a subj polygon + if (edge.WindDelta == 0 && edge.WindCnt != 1) return false; + break; + case pftNonZero: + if (Abs(edge.WindCnt) != 1) return false; + break; + case pftPositive: + if (edge.WindCnt != 1) return false; + break; + default: //pftNegative + if (edge.WindCnt != -1) return false; + } + + switch(m_ClipType) + { + case ctIntersection: + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 != 0); + case pftPositive: + return (edge.WindCnt2 > 0); + default: + return (edge.WindCnt2 < 0); + } + break; + case ctUnion: + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 == 0); + case pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + break; + case ctDifference: + if (edge.PolyTyp == ptSubject) + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 == 0); + case pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + else + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 != 0); + case pftPositive: + return (edge.WindCnt2 > 0); + default: + return (edge.WindCnt2 < 0); + } + break; + case ctXor: + if (edge.WindDelta == 0) //XOr always contributing unless open + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 == 0); + case pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + else + return true; + break; + default: + return true; + } +} +//------------------------------------------------------------------------------ + +OutPt* Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) +{ + OutPt* result; + TEdge *e, *prevE; + if (IsHorizontal(*e2) || ( e1->Dx > e2->Dx )) + { + result = AddOutPt(e1, Pt); + e2->OutIdx = e1->OutIdx; + e1->Side = esLeft; + e2->Side = esRight; + e = e1; + if (e->PrevInAEL == e2) + prevE = e2->PrevInAEL; + else + prevE = e->PrevInAEL; + } else + { + result = AddOutPt(e2, Pt); + e1->OutIdx = e2->OutIdx; + e1->Side = esRight; + e2->Side = esLeft; + e = e2; + if (e->PrevInAEL == e1) + prevE = e1->PrevInAEL; + else + prevE = e->PrevInAEL; + } + + if (prevE && prevE->OutIdx >= 0) + { + cInt xPrev = TopX(*prevE, Pt.Y); + cInt xE = TopX(*e, Pt.Y); + if (xPrev == xE && (e->WindDelta != 0) && (prevE->WindDelta != 0) && + SlopesEqual(IntPoint(xPrev, Pt.Y), prevE->Top, IntPoint(xE, Pt.Y), e->Top, m_UseFullRange)) + { + OutPt* outPt = AddOutPt(prevE, Pt); + AddJoin(result, outPt, e->Top); + } + } + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) +{ + AddOutPt( e1, Pt ); + if (e2->WindDelta == 0) AddOutPt(e2, Pt); + if( e1->OutIdx == e2->OutIdx ) + { + e1->OutIdx = Unassigned; + e2->OutIdx = Unassigned; + } + else if (e1->OutIdx < e2->OutIdx) + AppendPolygon(e1, e2); + else + AppendPolygon(e2, e1); +} +//------------------------------------------------------------------------------ + +void Clipper::AddEdgeToSEL(TEdge *edge) +{ + //SEL pointers in PEdge are reused to build a list of horizontal edges. + //However, we don't need to worry about order with horizontal edge processing. + if( !m_SortedEdges ) + { + m_SortedEdges = edge; + edge->PrevInSEL = 0; + edge->NextInSEL = 0; + } + else + { + edge->NextInSEL = m_SortedEdges; + edge->PrevInSEL = 0; + m_SortedEdges->PrevInSEL = edge; + m_SortedEdges = edge; + } +} +//------------------------------------------------------------------------------ + +bool Clipper::PopEdgeFromSEL(TEdge *&edge) +{ + if (!m_SortedEdges) return false; + edge = m_SortedEdges; + DeleteFromSEL(m_SortedEdges); + return true; +} +//------------------------------------------------------------------------------ + +void Clipper::CopyAELToSEL() +{ + TEdge* e = m_ActiveEdges; + m_SortedEdges = e; + while ( e ) + { + e->PrevInSEL = e->PrevInAEL; + e->NextInSEL = e->NextInAEL; + e = e->NextInAEL; + } +} +//------------------------------------------------------------------------------ + +void Clipper::AddJoin(OutPt *op1, OutPt *op2, const IntPoint OffPt) +{ + Join* j = new Join; + j->OutPt1 = op1; + j->OutPt2 = op2; + j->OffPt = OffPt; + m_Joins.push_back(j); +} +//------------------------------------------------------------------------------ + +void Clipper::ClearJoins() +{ + for (JoinList::size_type i = 0; i < m_Joins.size(); i++) + delete m_Joins[i]; + m_Joins.resize(0); +} +//------------------------------------------------------------------------------ + +void Clipper::ClearGhostJoins() +{ + for (JoinList::size_type i = 0; i < m_GhostJoins.size(); i++) + delete m_GhostJoins[i]; + m_GhostJoins.resize(0); +} +//------------------------------------------------------------------------------ + +void Clipper::AddGhostJoin(OutPt *op, const IntPoint OffPt) +{ + Join* j = new Join; + j->OutPt1 = op; + j->OutPt2 = 0; + j->OffPt = OffPt; + m_GhostJoins.push_back(j); +} +//------------------------------------------------------------------------------ + +void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) +{ + const LocalMinimum *lm; + while (PopLocalMinima(botY, lm)) + { + TEdge* lb = lm->LeftBound; + TEdge* rb = lm->RightBound; + + OutPt *Op1 = 0; + if (!lb) + { + //nb: don't insert LB into either AEL or SEL + InsertEdgeIntoAEL(rb, 0); + SetWindingCount(*rb); + if (IsContributing(*rb)) + Op1 = AddOutPt(rb, rb->Bot); + } + else if (!rb) + { + InsertEdgeIntoAEL(lb, 0); + SetWindingCount(*lb); + if (IsContributing(*lb)) + Op1 = AddOutPt(lb, lb->Bot); + InsertScanbeam(lb->Top.Y); + } + else + { + InsertEdgeIntoAEL(lb, 0); + InsertEdgeIntoAEL(rb, lb); + SetWindingCount( *lb ); + rb->WindCnt = lb->WindCnt; + rb->WindCnt2 = lb->WindCnt2; + if (IsContributing(*lb)) + Op1 = AddLocalMinPoly(lb, rb, lb->Bot); + InsertScanbeam(lb->Top.Y); + } + + if (rb) + { + if (IsHorizontal(*rb)) + { + AddEdgeToSEL(rb); + if (rb->NextInLML) + InsertScanbeam(rb->NextInLML->Top.Y); + } + else InsertScanbeam( rb->Top.Y ); + } + + if (!lb || !rb) continue; + + //if any output polygons share an edge, they'll need joining later ... + if (Op1 && IsHorizontal(*rb) && + m_GhostJoins.size() > 0 && (rb->WindDelta != 0)) + { + for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i) + { + Join* jr = m_GhostJoins[i]; + //if the horizontal Rb and a 'ghost' horizontal overlap, then convert + //the 'ghost' join to a real join ready for later ... + if (HorzSegmentsOverlap(jr->OutPt1->Pt.X, jr->OffPt.X, rb->Bot.X, rb->Top.X)) + AddJoin(jr->OutPt1, Op1, jr->OffPt); + } + } + + if (lb->OutIdx >= 0 && lb->PrevInAEL && + lb->PrevInAEL->Curr.X == lb->Bot.X && + lb->PrevInAEL->OutIdx >= 0 && + SlopesEqual(lb->PrevInAEL->Bot, lb->PrevInAEL->Top, lb->Curr, lb->Top, m_UseFullRange) && + (lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0)) + { + OutPt *Op2 = AddOutPt(lb->PrevInAEL, lb->Bot); + AddJoin(Op1, Op2, lb->Top); + } + + if(lb->NextInAEL != rb) + { + + if (rb->OutIdx >= 0 && rb->PrevInAEL->OutIdx >= 0 && + SlopesEqual(rb->PrevInAEL->Curr, rb->PrevInAEL->Top, rb->Curr, rb->Top, m_UseFullRange) && + (rb->WindDelta != 0) && (rb->PrevInAEL->WindDelta != 0)) + { + OutPt *Op2 = AddOutPt(rb->PrevInAEL, rb->Bot); + AddJoin(Op1, Op2, rb->Top); + } + + TEdge* e = lb->NextInAEL; + if (e) + { + while( e != rb ) + { + //nb: For calculating winding counts etc, IntersectEdges() assumes + //that param1 will be to the Right of param2 ABOVE the intersection ... + IntersectEdges(rb , e , lb->Curr); //order important here + e = e->NextInAEL; + } + } + } + + } +} +//------------------------------------------------------------------------------ + +void Clipper::DeleteFromSEL(TEdge *e) +{ + TEdge* SelPrev = e->PrevInSEL; + TEdge* SelNext = e->NextInSEL; + if( !SelPrev && !SelNext && (e != m_SortedEdges) ) return; //already deleted + if( SelPrev ) SelPrev->NextInSEL = SelNext; + else m_SortedEdges = SelNext; + if( SelNext ) SelNext->PrevInSEL = SelPrev; + e->NextInSEL = 0; + e->PrevInSEL = 0; +} +//------------------------------------------------------------------------------ + +#ifdef use_xyz +void Clipper::SetZ(IntPoint& pt, TEdge& e1, TEdge& e2) +{ + if (pt.Z != 0 || !m_ZFill) return; + else if (pt == e1.Bot) pt.Z = e1.Bot.Z; + else if (pt == e1.Top) pt.Z = e1.Top.Z; + else if (pt == e2.Bot) pt.Z = e2.Bot.Z; + else if (pt == e2.Top) pt.Z = e2.Top.Z; + else (*m_ZFill)(e1.Bot, e1.Top, e2.Bot, e2.Top, pt); +} +//------------------------------------------------------------------------------ +#endif + +void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt) +{ + bool e1Contributing = ( e1->OutIdx >= 0 ); + bool e2Contributing = ( e2->OutIdx >= 0 ); + +#ifdef use_xyz + SetZ(Pt, *e1, *e2); +#endif + +#ifdef use_lines + //if either edge is on an OPEN path ... + if (e1->WindDelta == 0 || e2->WindDelta == 0) + { + //ignore subject-subject open path intersections UNLESS they + //are both open paths, AND they are both 'contributing maximas' ... + if (e1->WindDelta == 0 && e2->WindDelta == 0) return; + + //if intersecting a subj line with a subj poly ... + else if (e1->PolyTyp == e2->PolyTyp && + e1->WindDelta != e2->WindDelta && m_ClipType == ctUnion) + { + if (e1->WindDelta == 0) + { + if (e2Contributing) + { + AddOutPt(e1, Pt); + if (e1Contributing) e1->OutIdx = Unassigned; + } + } + else + { + if (e1Contributing) + { + AddOutPt(e2, Pt); + if (e2Contributing) e2->OutIdx = Unassigned; + } + } + } + else if (e1->PolyTyp != e2->PolyTyp) + { + //toggle subj open path OutIdx on/off when Abs(clip.WndCnt) == 1 ... + if ((e1->WindDelta == 0) && abs(e2->WindCnt) == 1 && + (m_ClipType != ctUnion || e2->WindCnt2 == 0)) + { + AddOutPt(e1, Pt); + if (e1Contributing) e1->OutIdx = Unassigned; + } + else if ((e2->WindDelta == 0) && (abs(e1->WindCnt) == 1) && + (m_ClipType != ctUnion || e1->WindCnt2 == 0)) + { + AddOutPt(e2, Pt); + if (e2Contributing) e2->OutIdx = Unassigned; + } + } + return; + } +#endif + + //update winding counts... + //assumes that e1 will be to the Right of e2 ABOVE the intersection + if ( e1->PolyTyp == e2->PolyTyp ) + { + if ( IsEvenOddFillType( *e1) ) + { + int oldE1WindCnt = e1->WindCnt; + e1->WindCnt = e2->WindCnt; + e2->WindCnt = oldE1WindCnt; + } else + { + if (e1->WindCnt + e2->WindDelta == 0 ) e1->WindCnt = -e1->WindCnt; + else e1->WindCnt += e2->WindDelta; + if ( e2->WindCnt - e1->WindDelta == 0 ) e2->WindCnt = -e2->WindCnt; + else e2->WindCnt -= e1->WindDelta; + } + } else + { + if (!IsEvenOddFillType(*e2)) e1->WindCnt2 += e2->WindDelta; + else e1->WindCnt2 = ( e1->WindCnt2 == 0 ) ? 1 : 0; + if (!IsEvenOddFillType(*e1)) e2->WindCnt2 -= e1->WindDelta; + else e2->WindCnt2 = ( e2->WindCnt2 == 0 ) ? 1 : 0; + } + + PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; + if (e1->PolyTyp == ptSubject) + { + e1FillType = m_SubjFillType; + e1FillType2 = m_ClipFillType; + } else + { + e1FillType = m_ClipFillType; + e1FillType2 = m_SubjFillType; + } + if (e2->PolyTyp == ptSubject) + { + e2FillType = m_SubjFillType; + e2FillType2 = m_ClipFillType; + } else + { + e2FillType = m_ClipFillType; + e2FillType2 = m_SubjFillType; + } + + cInt e1Wc, e2Wc; + switch (e1FillType) + { + case pftPositive: e1Wc = e1->WindCnt; break; + case pftNegative: e1Wc = -e1->WindCnt; break; + default: e1Wc = Abs(e1->WindCnt); + } + switch(e2FillType) + { + case pftPositive: e2Wc = e2->WindCnt; break; + case pftNegative: e2Wc = -e2->WindCnt; break; + default: e2Wc = Abs(e2->WindCnt); + } + + if ( e1Contributing && e2Contributing ) + { + if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || + (e1->PolyTyp != e2->PolyTyp && m_ClipType != ctXor) ) + { + AddLocalMaxPoly(e1, e2, Pt); + } + else + { + AddOutPt(e1, Pt); + AddOutPt(e2, Pt); + SwapSides( *e1 , *e2 ); + SwapPolyIndexes( *e1 , *e2 ); + } + } + else if ( e1Contributing ) + { + if (e2Wc == 0 || e2Wc == 1) + { + AddOutPt(e1, Pt); + SwapSides(*e1, *e2); + SwapPolyIndexes(*e1, *e2); + } + } + else if ( e2Contributing ) + { + if (e1Wc == 0 || e1Wc == 1) + { + AddOutPt(e2, Pt); + SwapSides(*e1, *e2); + SwapPolyIndexes(*e1, *e2); + } + } + else if ( (e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) + { + //neither edge is currently contributing ... + + cInt e1Wc2, e2Wc2; + switch (e1FillType2) + { + case pftPositive: e1Wc2 = e1->WindCnt2; break; + case pftNegative : e1Wc2 = -e1->WindCnt2; break; + default: e1Wc2 = Abs(e1->WindCnt2); + } + switch (e2FillType2) + { + case pftPositive: e2Wc2 = e2->WindCnt2; break; + case pftNegative: e2Wc2 = -e2->WindCnt2; break; + default: e2Wc2 = Abs(e2->WindCnt2); + } + + if (e1->PolyTyp != e2->PolyTyp) + { + AddLocalMinPoly(e1, e2, Pt); + } + else if (e1Wc == 1 && e2Wc == 1) + switch( m_ClipType ) { + case ctIntersection: + if (e1Wc2 > 0 && e2Wc2 > 0) + AddLocalMinPoly(e1, e2, Pt); + break; + case ctUnion: + if ( e1Wc2 <= 0 && e2Wc2 <= 0 ) + AddLocalMinPoly(e1, e2, Pt); + break; + case ctDifference: + if (((e1->PolyTyp == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || + ((e1->PolyTyp == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) + AddLocalMinPoly(e1, e2, Pt); + break; + case ctXor: + AddLocalMinPoly(e1, e2, Pt); + } + else + SwapSides( *e1, *e2 ); + } +} +//------------------------------------------------------------------------------ + +void Clipper::SetHoleState(TEdge *e, OutRec *outrec) +{ + TEdge *e2 = e->PrevInAEL; + TEdge *eTmp = 0; + while (e2) + { + if (e2->OutIdx >= 0 && e2->WindDelta != 0) + { + if (!eTmp) eTmp = e2; + else if (eTmp->OutIdx == e2->OutIdx) eTmp = 0; + } + e2 = e2->PrevInAEL; + } + if (!eTmp) + { + outrec->FirstLeft = 0; + outrec->IsHole = false; + } + else + { + outrec->FirstLeft = m_PolyOuts[eTmp->OutIdx]; + outrec->IsHole = !outrec->FirstLeft->IsHole; + } +} +//------------------------------------------------------------------------------ + +OutRec* GetLowermostRec(OutRec *outRec1, OutRec *outRec2) +{ + //work out which polygon fragment has the correct hole state ... + if (!outRec1->BottomPt) + outRec1->BottomPt = GetBottomPt(outRec1->Pts); + if (!outRec2->BottomPt) + outRec2->BottomPt = GetBottomPt(outRec2->Pts); + OutPt *OutPt1 = outRec1->BottomPt; + OutPt *OutPt2 = outRec2->BottomPt; + if (OutPt1->Pt.Y > OutPt2->Pt.Y) return outRec1; + else if (OutPt1->Pt.Y < OutPt2->Pt.Y) return outRec2; + else if (OutPt1->Pt.X < OutPt2->Pt.X) return outRec1; + else if (OutPt1->Pt.X > OutPt2->Pt.X) return outRec2; + else if (OutPt1->Next == OutPt1) return outRec2; + else if (OutPt2->Next == OutPt2) return outRec1; + else if (FirstIsBottomPt(OutPt1, OutPt2)) return outRec1; + else return outRec2; +} +//------------------------------------------------------------------------------ + +bool OutRec1RightOfOutRec2(OutRec* outRec1, OutRec* outRec2) +{ + do + { + outRec1 = outRec1->FirstLeft; + if (outRec1 == outRec2) return true; + } while (outRec1); + return false; +} +//------------------------------------------------------------------------------ + +OutRec* Clipper::GetOutRec(int Idx) +{ + OutRec* outrec = m_PolyOuts[Idx]; + while (outrec != m_PolyOuts[outrec->Idx]) + outrec = m_PolyOuts[outrec->Idx]; + return outrec; +} +//------------------------------------------------------------------------------ + +void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) +{ + //get the start and ends of both output polygons ... + OutRec *outRec1 = m_PolyOuts[e1->OutIdx]; + OutRec *outRec2 = m_PolyOuts[e2->OutIdx]; + + OutRec *holeStateRec; + if (OutRec1RightOfOutRec2(outRec1, outRec2)) + holeStateRec = outRec2; + else if (OutRec1RightOfOutRec2(outRec2, outRec1)) + holeStateRec = outRec1; + else + holeStateRec = GetLowermostRec(outRec1, outRec2); + + //get the start and ends of both output polygons and + //join e2 poly onto e1 poly and delete pointers to e2 ... + + OutPt* p1_lft = outRec1->Pts; + OutPt* p1_rt = p1_lft->Prev; + OutPt* p2_lft = outRec2->Pts; + OutPt* p2_rt = p2_lft->Prev; + + //join e2 poly onto e1 poly and delete pointers to e2 ... + if( e1->Side == esLeft ) + { + if( e2->Side == esLeft ) + { + //z y x a b c + ReversePolyPtLinks(p2_lft); + p2_lft->Next = p1_lft; + p1_lft->Prev = p2_lft; + p1_rt->Next = p2_rt; + p2_rt->Prev = p1_rt; + outRec1->Pts = p2_rt; + } else + { + //x y z a b c + p2_rt->Next = p1_lft; + p1_lft->Prev = p2_rt; + p2_lft->Prev = p1_rt; + p1_rt->Next = p2_lft; + outRec1->Pts = p2_lft; + } + } else + { + if( e2->Side == esRight ) + { + //a b c z y x + ReversePolyPtLinks(p2_lft); + p1_rt->Next = p2_rt; + p2_rt->Prev = p1_rt; + p2_lft->Next = p1_lft; + p1_lft->Prev = p2_lft; + } else + { + //a b c x y z + p1_rt->Next = p2_lft; + p2_lft->Prev = p1_rt; + p1_lft->Prev = p2_rt; + p2_rt->Next = p1_lft; + } + } + + outRec1->BottomPt = 0; + if (holeStateRec == outRec2) + { + if (outRec2->FirstLeft != outRec1) + outRec1->FirstLeft = outRec2->FirstLeft; + outRec1->IsHole = outRec2->IsHole; + } + outRec2->Pts = 0; + outRec2->BottomPt = 0; + outRec2->FirstLeft = outRec1; + + int OKIdx = e1->OutIdx; + int ObsoleteIdx = e2->OutIdx; + + e1->OutIdx = Unassigned; //nb: safe because we only get here via AddLocalMaxPoly + e2->OutIdx = Unassigned; + + TEdge* e = m_ActiveEdges; + while( e ) + { + if( e->OutIdx == ObsoleteIdx ) + { + e->OutIdx = OKIdx; + e->Side = e1->Side; + break; + } + e = e->NextInAEL; + } + + outRec2->Idx = outRec1->Idx; +} +//------------------------------------------------------------------------------ + +OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt) +{ + if( e->OutIdx < 0 ) + { + OutRec *outRec = CreateOutRec(); + outRec->IsOpen = (e->WindDelta == 0); + OutPt* newOp = new OutPt; + outRec->Pts = newOp; + newOp->Idx = outRec->Idx; + newOp->Pt = pt; + newOp->Next = newOp; + newOp->Prev = newOp; + if (!outRec->IsOpen) + SetHoleState(e, outRec); + e->OutIdx = outRec->Idx; + return newOp; + } else + { + OutRec *outRec = m_PolyOuts[e->OutIdx]; + //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most' + OutPt* op = outRec->Pts; + + bool ToFront = (e->Side == esLeft); + if (ToFront && (pt == op->Pt)) return op; + else if (!ToFront && (pt == op->Prev->Pt)) return op->Prev; + + OutPt* newOp = new OutPt; + newOp->Idx = outRec->Idx; + newOp->Pt = pt; + newOp->Next = op; + newOp->Prev = op->Prev; + newOp->Prev->Next = newOp; + op->Prev = newOp; + if (ToFront) outRec->Pts = newOp; + return newOp; + } +} +//------------------------------------------------------------------------------ + +OutPt* Clipper::GetLastOutPt(TEdge *e) +{ + OutRec *outRec = m_PolyOuts[e->OutIdx]; + if (e->Side == esLeft) + return outRec->Pts; + else + return outRec->Pts->Prev; +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessHorizontals() +{ + TEdge* horzEdge; + while (PopEdgeFromSEL(horzEdge)) + ProcessHorizontal(horzEdge); +} +//------------------------------------------------------------------------------ + +inline bool IsMinima(TEdge *e) +{ + return e && (e->Prev->NextInLML != e) && (e->Next->NextInLML != e); +} +//------------------------------------------------------------------------------ + +inline bool IsMaxima(TEdge *e, const cInt Y) +{ + return e && e->Top.Y == Y && !e->NextInLML; +} +//------------------------------------------------------------------------------ + +inline bool IsIntermediate(TEdge *e, const cInt Y) +{ + return e->Top.Y == Y && e->NextInLML; +} +//------------------------------------------------------------------------------ + +TEdge *GetMaximaPair(TEdge *e) +{ + if ((e->Next->Top == e->Top) && !e->Next->NextInLML) + return e->Next; + else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML) + return e->Prev; + else return 0; +} +//------------------------------------------------------------------------------ + +TEdge *GetMaximaPairEx(TEdge *e) +{ + //as GetMaximaPair() but returns 0 if MaxPair isn't in AEL (unless it's horizontal) + TEdge* result = GetMaximaPair(e); + if (result && (result->OutIdx == Skip || + (result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result)))) return 0; + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::SwapPositionsInSEL(TEdge *Edge1, TEdge *Edge2) +{ + if( !( Edge1->NextInSEL ) && !( Edge1->PrevInSEL ) ) return; + if( !( Edge2->NextInSEL ) && !( Edge2->PrevInSEL ) ) return; + + if( Edge1->NextInSEL == Edge2 ) + { + TEdge* Next = Edge2->NextInSEL; + if( Next ) Next->PrevInSEL = Edge1; + TEdge* Prev = Edge1->PrevInSEL; + if( Prev ) Prev->NextInSEL = Edge2; + Edge2->PrevInSEL = Prev; + Edge2->NextInSEL = Edge1; + Edge1->PrevInSEL = Edge2; + Edge1->NextInSEL = Next; + } + else if( Edge2->NextInSEL == Edge1 ) + { + TEdge* Next = Edge1->NextInSEL; + if( Next ) Next->PrevInSEL = Edge2; + TEdge* Prev = Edge2->PrevInSEL; + if( Prev ) Prev->NextInSEL = Edge1; + Edge1->PrevInSEL = Prev; + Edge1->NextInSEL = Edge2; + Edge2->PrevInSEL = Edge1; + Edge2->NextInSEL = Next; + } + else + { + TEdge* Next = Edge1->NextInSEL; + TEdge* Prev = Edge1->PrevInSEL; + Edge1->NextInSEL = Edge2->NextInSEL; + if( Edge1->NextInSEL ) Edge1->NextInSEL->PrevInSEL = Edge1; + Edge1->PrevInSEL = Edge2->PrevInSEL; + if( Edge1->PrevInSEL ) Edge1->PrevInSEL->NextInSEL = Edge1; + Edge2->NextInSEL = Next; + if( Edge2->NextInSEL ) Edge2->NextInSEL->PrevInSEL = Edge2; + Edge2->PrevInSEL = Prev; + if( Edge2->PrevInSEL ) Edge2->PrevInSEL->NextInSEL = Edge2; + } + + if( !Edge1->PrevInSEL ) m_SortedEdges = Edge1; + else if( !Edge2->PrevInSEL ) m_SortedEdges = Edge2; +} +//------------------------------------------------------------------------------ + +TEdge* GetNextInAEL(TEdge *e, Direction dir) +{ + return dir == dLeftToRight ? e->NextInAEL : e->PrevInAEL; +} +//------------------------------------------------------------------------------ + +void GetHorzDirection(TEdge& HorzEdge, Direction& Dir, cInt& Left, cInt& Right) +{ + if (HorzEdge.Bot.X < HorzEdge.Top.X) + { + Left = HorzEdge.Bot.X; + Right = HorzEdge.Top.X; + Dir = dLeftToRight; + } else + { + Left = HorzEdge.Top.X; + Right = HorzEdge.Bot.X; + Dir = dRightToLeft; + } +} +//------------------------------------------------------------------------ + +/******************************************************************************* +* Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or * +* Bottom of a scanbeam) are processed as if layered. The order in which HEs * +* are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#] * +* (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs), * +* and with other non-horizontal edges [*]. Once these intersections are * +* processed, intermediate HEs then 'promote' the Edge above (NextInLML) into * +* the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. * +*******************************************************************************/ + +void Clipper::ProcessHorizontal(TEdge *horzEdge) +{ + Direction dir; + cInt horzLeft, horzRight; + bool IsOpen = (horzEdge->WindDelta == 0); + + GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); + + TEdge* eLastHorz = horzEdge, *eMaxPair = 0; + while (eLastHorz->NextInLML && IsHorizontal(*eLastHorz->NextInLML)) + eLastHorz = eLastHorz->NextInLML; + if (!eLastHorz->NextInLML) + eMaxPair = GetMaximaPair(eLastHorz); + + MaximaList::const_iterator maxIt; + MaximaList::const_reverse_iterator maxRit; + if (m_Maxima.size() > 0) + { + //get the first maxima in range (X) ... + if (dir == dLeftToRight) + { + maxIt = m_Maxima.begin(); + while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.X) maxIt++; + if (maxIt != m_Maxima.end() && *maxIt >= eLastHorz->Top.X) + maxIt = m_Maxima.end(); + } + else + { + maxRit = m_Maxima.rbegin(); + while (maxRit != m_Maxima.rend() && *maxRit > horzEdge->Bot.X) maxRit++; + if (maxRit != m_Maxima.rend() && *maxRit <= eLastHorz->Top.X) + maxRit = m_Maxima.rend(); + } + } + + OutPt* op1 = 0; + + for (;;) //loop through consec. horizontal edges + { + + bool IsLastHorz = (horzEdge == eLastHorz); + TEdge* e = GetNextInAEL(horzEdge, dir); + while(e) + { + + //this code block inserts extra coords into horizontal edges (in output + //polygons) whereever maxima touch these horizontal edges. This helps + //'simplifying' polygons (ie if the Simplify property is set). + if (m_Maxima.size() > 0) + { + if (dir == dLeftToRight) + { + while (maxIt != m_Maxima.end() && *maxIt < e->Curr.X) + { + if (horzEdge->OutIdx >= 0 && !IsOpen) + AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.Y)); + maxIt++; + } + } + else + { + while (maxRit != m_Maxima.rend() && *maxRit > e->Curr.X) + { + if (horzEdge->OutIdx >= 0 && !IsOpen) + AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.Y)); + maxRit++; + } + } + }; + + if ((dir == dLeftToRight && e->Curr.X > horzRight) || + (dir == dRightToLeft && e->Curr.X < horzLeft)) break; + + //Also break if we've got to the end of an intermediate horizontal edge ... + //nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal. + if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML && + e->Dx < horzEdge->NextInLML->Dx) break; + + if (horzEdge->OutIdx >= 0 && !IsOpen) //note: may be done multiple times + { + op1 = AddOutPt(horzEdge, e->Curr); + TEdge* eNextHorz = m_SortedEdges; + while (eNextHorz) + { + if (eNextHorz->OutIdx >= 0 && + HorzSegmentsOverlap(horzEdge->Bot.X, + horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) + { + OutPt* op2 = GetLastOutPt(eNextHorz); + AddJoin(op2, op1, eNextHorz->Top); + } + eNextHorz = eNextHorz->NextInSEL; + } + AddGhostJoin(op1, horzEdge->Bot); + } + + //OK, so far we're still in range of the horizontal Edge but make sure + //we're at the last of consec. horizontals when matching with eMaxPair + if(e == eMaxPair && IsLastHorz) + { + if (horzEdge->OutIdx >= 0) + AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge->Top); + DeleteFromAEL(horzEdge); + DeleteFromAEL(eMaxPair); + return; + } + + if(dir == dLeftToRight) + { + IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); + IntersectEdges(horzEdge, e, Pt); + } + else + { + IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); + IntersectEdges( e, horzEdge, Pt); + } + TEdge* eNext = GetNextInAEL(e, dir); + SwapPositionsInAEL( horzEdge, e ); + e = eNext; + } //end while(e) + + //Break out of loop if HorzEdge.NextInLML is not also horizontal ... + if (!horzEdge->NextInLML || !IsHorizontal(*horzEdge->NextInLML)) break; + + UpdateEdgeIntoAEL(horzEdge); + if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Bot); + GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); + + } //end for (;;) + + if (horzEdge->OutIdx >= 0 && !op1) + { + op1 = GetLastOutPt(horzEdge); + TEdge* eNextHorz = m_SortedEdges; + while (eNextHorz) + { + if (eNextHorz->OutIdx >= 0 && + HorzSegmentsOverlap(horzEdge->Bot.X, + horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) + { + OutPt* op2 = GetLastOutPt(eNextHorz); + AddJoin(op2, op1, eNextHorz->Top); + } + eNextHorz = eNextHorz->NextInSEL; + } + AddGhostJoin(op1, horzEdge->Top); + } + + if (horzEdge->NextInLML) + { + if(horzEdge->OutIdx >= 0) + { + op1 = AddOutPt( horzEdge, horzEdge->Top); + UpdateEdgeIntoAEL(horzEdge); + if (horzEdge->WindDelta == 0) return; + //nb: HorzEdge is no longer horizontal here + TEdge* ePrev = horzEdge->PrevInAEL; + TEdge* eNext = horzEdge->NextInAEL; + if (ePrev && ePrev->Curr.X == horzEdge->Bot.X && + ePrev->Curr.Y == horzEdge->Bot.Y && ePrev->WindDelta != 0 && + (ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && + SlopesEqual(*horzEdge, *ePrev, m_UseFullRange))) + { + OutPt* op2 = AddOutPt(ePrev, horzEdge->Bot); + AddJoin(op1, op2, horzEdge->Top); + } + else if (eNext && eNext->Curr.X == horzEdge->Bot.X && + eNext->Curr.Y == horzEdge->Bot.Y && eNext->WindDelta != 0 && + eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && + SlopesEqual(*horzEdge, *eNext, m_UseFullRange)) + { + OutPt* op2 = AddOutPt(eNext, horzEdge->Bot); + AddJoin(op1, op2, horzEdge->Top); + } + } + else + UpdateEdgeIntoAEL(horzEdge); + } + else + { + if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Top); + DeleteFromAEL(horzEdge); + } +} +//------------------------------------------------------------------------------ + +bool Clipper::ProcessIntersections(const cInt topY) +{ + if( !m_ActiveEdges ) return true; + try { + BuildIntersectList(topY); + size_t IlSize = m_IntersectList.size(); + if (IlSize == 0) return true; + if (IlSize == 1 || FixupIntersectionOrder()) ProcessIntersectList(); + else return false; + } + catch(...) + { + m_SortedEdges = 0; + DisposeIntersectNodes(); + throw clipperException("ProcessIntersections error"); + } + m_SortedEdges = 0; + return true; +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeIntersectNodes() +{ + for (size_t i = 0; i < m_IntersectList.size(); ++i ) + delete m_IntersectList[i]; + m_IntersectList.clear(); +} +//------------------------------------------------------------------------------ + +void Clipper::BuildIntersectList(const cInt topY) +{ + if ( !m_ActiveEdges ) return; + + //prepare for sorting ... + TEdge* e = m_ActiveEdges; + m_SortedEdges = e; + while( e ) + { + e->PrevInSEL = e->PrevInAEL; + e->NextInSEL = e->NextInAEL; + e->Curr.X = TopX( *e, topY ); + e = e->NextInAEL; + } + + //bubblesort ... + bool isModified; + do + { + isModified = false; + e = m_SortedEdges; + while( e->NextInSEL ) + { + TEdge *eNext = e->NextInSEL; + IntPoint Pt; + if(e->Curr.X > eNext->Curr.X) + { + IntersectPoint(*e, *eNext, Pt); + if (Pt.Y < topY) Pt = IntPoint(TopX(*e, topY), topY); + IntersectNode * newNode = new IntersectNode; + newNode->Edge1 = e; + newNode->Edge2 = eNext; + newNode->Pt = Pt; + m_IntersectList.push_back(newNode); + + SwapPositionsInSEL(e, eNext); + isModified = true; + } + else + e = eNext; + } + if( e->PrevInSEL ) e->PrevInSEL->NextInSEL = 0; + else break; + } + while ( isModified ); + m_SortedEdges = 0; //important +} +//------------------------------------------------------------------------------ + + +void Clipper::ProcessIntersectList() +{ + for (size_t i = 0; i < m_IntersectList.size(); ++i) + { + IntersectNode* iNode = m_IntersectList[i]; + { + IntersectEdges( iNode->Edge1, iNode->Edge2, iNode->Pt); + SwapPositionsInAEL( iNode->Edge1 , iNode->Edge2 ); + } + delete iNode; + } + m_IntersectList.clear(); +} +//------------------------------------------------------------------------------ + +bool IntersectListSort(IntersectNode* node1, IntersectNode* node2) +{ + return node2->Pt.Y < node1->Pt.Y; +} +//------------------------------------------------------------------------------ + +inline bool EdgesAdjacent(const IntersectNode &inode) +{ + return (inode.Edge1->NextInSEL == inode.Edge2) || + (inode.Edge1->PrevInSEL == inode.Edge2); +} +//------------------------------------------------------------------------------ + +bool Clipper::FixupIntersectionOrder() +{ + //pre-condition: intersections are sorted Bottom-most first. + //Now it's crucial that intersections are made only between adjacent edges, + //so to ensure this the order of intersections may need adjusting ... + CopyAELToSEL(); + std::sort(m_IntersectList.begin(), m_IntersectList.end(), IntersectListSort); + size_t cnt = m_IntersectList.size(); + for (size_t i = 0; i < cnt; ++i) + { + if (!EdgesAdjacent(*m_IntersectList[i])) + { + size_t j = i + 1; + while (j < cnt && !EdgesAdjacent(*m_IntersectList[j])) j++; + if (j == cnt) return false; + std::swap(m_IntersectList[i], m_IntersectList[j]); + } + SwapPositionsInSEL(m_IntersectList[i]->Edge1, m_IntersectList[i]->Edge2); + } + return true; +} +//------------------------------------------------------------------------------ + +void Clipper::DoMaxima(TEdge *e) +{ + TEdge* eMaxPair = GetMaximaPairEx(e); + if (!eMaxPair) + { + if (e->OutIdx >= 0) + AddOutPt(e, e->Top); + DeleteFromAEL(e); + return; + } + + TEdge* eNext = e->NextInAEL; + while(eNext && eNext != eMaxPair) + { + IntersectEdges(e, eNext, e->Top); + SwapPositionsInAEL(e, eNext); + eNext = e->NextInAEL; + } + + if(e->OutIdx == Unassigned && eMaxPair->OutIdx == Unassigned) + { + DeleteFromAEL(e); + DeleteFromAEL(eMaxPair); + } + else if( e->OutIdx >= 0 && eMaxPair->OutIdx >= 0 ) + { + if (e->OutIdx >= 0) AddLocalMaxPoly(e, eMaxPair, e->Top); + DeleteFromAEL(e); + DeleteFromAEL(eMaxPair); + } +#ifdef use_lines + else if (e->WindDelta == 0) + { + if (e->OutIdx >= 0) + { + AddOutPt(e, e->Top); + e->OutIdx = Unassigned; + } + DeleteFromAEL(e); + + if (eMaxPair->OutIdx >= 0) + { + AddOutPt(eMaxPair, e->Top); + eMaxPair->OutIdx = Unassigned; + } + DeleteFromAEL(eMaxPair); + } +#endif + else throw clipperException("DoMaxima error"); +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) +{ + TEdge* e = m_ActiveEdges; + while( e ) + { + //1. process maxima, treating them as if they're 'bent' horizontal edges, + // but exclude maxima with horizontal edges. nb: e can't be a horizontal. + bool IsMaximaEdge = IsMaxima(e, topY); + + if(IsMaximaEdge) + { + TEdge* eMaxPair = GetMaximaPairEx(e); + IsMaximaEdge = (!eMaxPair || !IsHorizontal(*eMaxPair)); + } + + if(IsMaximaEdge) + { + if (m_StrictSimple) m_Maxima.push_back(e->Top.X); + TEdge* ePrev = e->PrevInAEL; + DoMaxima(e); + if( !ePrev ) e = m_ActiveEdges; + else e = ePrev->NextInAEL; + } + else + { + //2. promote horizontal edges, otherwise update Curr.X and Curr.Y ... + if (IsIntermediate(e, topY) && IsHorizontal(*e->NextInLML)) + { + UpdateEdgeIntoAEL(e); + if (e->OutIdx >= 0) + AddOutPt(e, e->Bot); + AddEdgeToSEL(e); + } + else + { + e->Curr.X = TopX( *e, topY ); + e->Curr.Y = topY; + } + + //When StrictlySimple and 'e' is being touched by another edge, then + //make sure both edges have a vertex here ... + if (m_StrictSimple) + { + TEdge* ePrev = e->PrevInAEL; + if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev && (ePrev->OutIdx >= 0) && + (ePrev->Curr.X == e->Curr.X) && (ePrev->WindDelta != 0)) + { + IntPoint pt = e->Curr; +#ifdef use_xyz + SetZ(pt, *ePrev, *e); +#endif + OutPt* op = AddOutPt(ePrev, pt); + OutPt* op2 = AddOutPt(e, pt); + AddJoin(op, op2, pt); //StrictlySimple (type-3) join + } + } + + e = e->NextInAEL; + } + } + + //3. Process horizontals at the Top of the scanbeam ... + m_Maxima.sort(); + ProcessHorizontals(); + m_Maxima.clear(); + + //4. Promote intermediate vertices ... + e = m_ActiveEdges; + while(e) + { + if(IsIntermediate(e, topY)) + { + OutPt* op = 0; + if( e->OutIdx >= 0 ) + op = AddOutPt(e, e->Top); + UpdateEdgeIntoAEL(e); + + //if output polygons share an edge, they'll need joining later ... + TEdge* ePrev = e->PrevInAEL; + TEdge* eNext = e->NextInAEL; + if (ePrev && ePrev->Curr.X == e->Bot.X && + ePrev->Curr.Y == e->Bot.Y && op && + ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && + SlopesEqual(e->Curr, e->Top, ePrev->Curr, ePrev->Top, m_UseFullRange) && + (e->WindDelta != 0) && (ePrev->WindDelta != 0)) + { + OutPt* op2 = AddOutPt(ePrev, e->Bot); + AddJoin(op, op2, e->Top); + } + else if (eNext && eNext->Curr.X == e->Bot.X && + eNext->Curr.Y == e->Bot.Y && op && + eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && + SlopesEqual(e->Curr, e->Top, eNext->Curr, eNext->Top, m_UseFullRange) && + (e->WindDelta != 0) && (eNext->WindDelta != 0)) + { + OutPt* op2 = AddOutPt(eNext, e->Bot); + AddJoin(op, op2, e->Top); + } + } + e = e->NextInAEL; + } +} +//------------------------------------------------------------------------------ + +void Clipper::FixupOutPolyline(OutRec &outrec) +{ + OutPt *pp = outrec.Pts; + OutPt *lastPP = pp->Prev; + while (pp != lastPP) + { + pp = pp->Next; + if (pp->Pt == pp->Prev->Pt) + { + if (pp == lastPP) lastPP = pp->Prev; + OutPt *tmpPP = pp->Prev; + tmpPP->Next = pp->Next; + pp->Next->Prev = tmpPP; + delete pp; + pp = tmpPP; + } + } + + if (pp == pp->Prev) + { + DisposeOutPts(pp); + outrec.Pts = 0; + return; + } +} +//------------------------------------------------------------------------------ + +void Clipper::FixupOutPolygon(OutRec &outrec) +{ + //FixupOutPolygon() - removes duplicate points and simplifies consecutive + //parallel edges by removing the middle vertex. + OutPt *lastOK = 0; + outrec.BottomPt = 0; + OutPt *pp = outrec.Pts; + bool preserveCol = m_PreserveCollinear || m_StrictSimple; + + for (;;) + { + if (pp->Prev == pp || pp->Prev == pp->Next) + { + DisposeOutPts(pp); + outrec.Pts = 0; + return; + } + + //test for duplicate points and collinear edges ... + if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) || + (SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) && + (!preserveCol || !Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt)))) + { + lastOK = 0; + OutPt *tmp = pp; + pp->Prev->Next = pp->Next; + pp->Next->Prev = pp->Prev; + pp = pp->Prev; + delete tmp; + } + else if (pp == lastOK) break; + else + { + if (!lastOK) lastOK = pp; + pp = pp->Next; + } + } + outrec.Pts = pp; +} +//------------------------------------------------------------------------------ + +int PointCount(OutPt *Pts) +{ + if (!Pts) return 0; + int result = 0; + OutPt* p = Pts; + do + { + result++; + p = p->Next; + } + while (p != Pts); + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::BuildResult(Paths &polys) +{ + polys.reserve(m_PolyOuts.size()); + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + if (!m_PolyOuts[i]->Pts) continue; + Path pg; + OutPt* p = m_PolyOuts[i]->Pts->Prev; + int cnt = PointCount(p); + if (cnt < 2) continue; + pg.reserve(cnt); + for (int i = 0; i < cnt; ++i) + { + pg.push_back(p->Pt); + p = p->Prev; + } + polys.push_back(pg); + } +} +//------------------------------------------------------------------------------ + +void Clipper::BuildResult2(PolyTree& polytree) +{ + polytree.Clear(); + polytree.AllNodes.reserve(m_PolyOuts.size()); + //add each output polygon/contour to polytree ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) + { + OutRec* outRec = m_PolyOuts[i]; + int cnt = PointCount(outRec->Pts); + if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3)) continue; + FixHoleLinkage(*outRec); + PolyNode* pn = new PolyNode(); + //nb: polytree takes ownership of all the PolyNodes + polytree.AllNodes.push_back(pn); + outRec->PolyNd = pn; + pn->Parent = 0; + pn->Index = 0; + pn->Contour.reserve(cnt); + OutPt *op = outRec->Pts->Prev; + for (int j = 0; j < cnt; j++) + { + pn->Contour.push_back(op->Pt); + op = op->Prev; + } + } + + //fixup PolyNode links etc ... + polytree.Childs.reserve(m_PolyOuts.size()); + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) + { + OutRec* outRec = m_PolyOuts[i]; + if (!outRec->PolyNd) continue; + if (outRec->IsOpen) + { + outRec->PolyNd->m_IsOpen = true; + polytree.AddChild(*outRec->PolyNd); + } + else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd) + outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd); + else + polytree.AddChild(*outRec->PolyNd); + } +} +//------------------------------------------------------------------------------ + +void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) +{ + //just swap the contents (because fIntersectNodes is a single-linked-list) + IntersectNode inode = int1; //gets a copy of Int1 + int1.Edge1 = int2.Edge1; + int1.Edge2 = int2.Edge2; + int1.Pt = int2.Pt; + int2.Edge1 = inode.Edge1; + int2.Edge2 = inode.Edge2; + int2.Pt = inode.Pt; +} +//------------------------------------------------------------------------------ + +inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) +{ + if (e2.Curr.X == e1.Curr.X) + { + if (e2.Top.Y > e1.Top.Y) + return e2.Top.X < TopX(e1, e2.Top.Y); + else return e1.Top.X > TopX(e2, e1.Top.Y); + } + else return e2.Curr.X < e1.Curr.X; +} +//------------------------------------------------------------------------------ + +bool GetOverlap(const cInt a1, const cInt a2, const cInt b1, const cInt b2, + cInt& Left, cInt& Right) +{ + if (a1 < a2) + { + if (b1 < b2) {Left = std::max(a1,b1); Right = std::min(a2,b2);} + else {Left = std::max(a1,b2); Right = std::min(a2,b1);} + } + else + { + if (b1 < b2) {Left = std::max(a2,b1); Right = std::min(a1,b2);} + else {Left = std::max(a2,b2); Right = std::min(a1,b1);} + } + return Left < Right; +} +//------------------------------------------------------------------------------ + +inline void UpdateOutPtIdxs(OutRec& outrec) +{ + OutPt* op = outrec.Pts; + do + { + op->Idx = outrec.Idx; + op = op->Prev; + } + while(op != outrec.Pts); +} +//------------------------------------------------------------------------------ + +void Clipper::InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge) +{ + if(!m_ActiveEdges) + { + edge->PrevInAEL = 0; + edge->NextInAEL = 0; + m_ActiveEdges = edge; + } + else if(!startEdge && E2InsertsBeforeE1(*m_ActiveEdges, *edge)) + { + edge->PrevInAEL = 0; + edge->NextInAEL = m_ActiveEdges; + m_ActiveEdges->PrevInAEL = edge; + m_ActiveEdges = edge; + } + else + { + if(!startEdge) startEdge = m_ActiveEdges; + while(startEdge->NextInAEL && + !E2InsertsBeforeE1(*startEdge->NextInAEL , *edge)) + startEdge = startEdge->NextInAEL; + edge->NextInAEL = startEdge->NextInAEL; + if(startEdge->NextInAEL) startEdge->NextInAEL->PrevInAEL = edge; + edge->PrevInAEL = startEdge; + startEdge->NextInAEL = edge; + } +} +//---------------------------------------------------------------------- + +OutPt* DupOutPt(OutPt* outPt, bool InsertAfter) +{ + OutPt* result = new OutPt; + result->Pt = outPt->Pt; + result->Idx = outPt->Idx; + if (InsertAfter) + { + result->Next = outPt->Next; + result->Prev = outPt; + outPt->Next->Prev = result; + outPt->Next = result; + } + else + { + result->Prev = outPt->Prev; + result->Next = outPt; + outPt->Prev->Next = result; + outPt->Prev = result; + } + return result; +} +//------------------------------------------------------------------------------ + +bool JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, + const IntPoint Pt, bool DiscardLeft) +{ + Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight); + Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : dLeftToRight); + if (Dir1 == Dir2) return false; + + //When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we + //want Op1b to be on the Right. (And likewise with Op2 and Op2b.) + //So, to facilitate this while inserting Op1b and Op2b ... + //when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b, + //otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.) + if (Dir1 == dLeftToRight) + { + while (op1->Next->Pt.X <= Pt.X && + op1->Next->Pt.X >= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) + op1 = op1->Next; + if (DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; + op1b = DupOutPt(op1, !DiscardLeft); + if (op1b->Pt != Pt) + { + op1 = op1b; + op1->Pt = Pt; + op1b = DupOutPt(op1, !DiscardLeft); + } + } + else + { + while (op1->Next->Pt.X >= Pt.X && + op1->Next->Pt.X <= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) + op1 = op1->Next; + if (!DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; + op1b = DupOutPt(op1, DiscardLeft); + if (op1b->Pt != Pt) + { + op1 = op1b; + op1->Pt = Pt; + op1b = DupOutPt(op1, DiscardLeft); + } + } + + if (Dir2 == dLeftToRight) + { + while (op2->Next->Pt.X <= Pt.X && + op2->Next->Pt.X >= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) + op2 = op2->Next; + if (DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; + op2b = DupOutPt(op2, !DiscardLeft); + if (op2b->Pt != Pt) + { + op2 = op2b; + op2->Pt = Pt; + op2b = DupOutPt(op2, !DiscardLeft); + }; + } else + { + while (op2->Next->Pt.X >= Pt.X && + op2->Next->Pt.X <= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) + op2 = op2->Next; + if (!DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; + op2b = DupOutPt(op2, DiscardLeft); + if (op2b->Pt != Pt) + { + op2 = op2b; + op2->Pt = Pt; + op2b = DupOutPt(op2, DiscardLeft); + }; + }; + + if ((Dir1 == dLeftToRight) == DiscardLeft) + { + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + } + else + { + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + } + return true; +} +//------------------------------------------------------------------------------ + +bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2) +{ + OutPt *op1 = j->OutPt1, *op1b; + OutPt *op2 = j->OutPt2, *op2b; + + //There are 3 kinds of joins for output polygons ... + //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere + //along (horizontal) collinear edges (& Join.OffPt is on the same horizontal). + //2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same + //location at the Bottom of the overlapping segment (& Join.OffPt is above). + //3. StrictSimple joins where edges touch but are not collinear and where + //Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point. + bool isHorizontal = (j->OutPt1->Pt.Y == j->OffPt.Y); + + if (isHorizontal && (j->OffPt == j->OutPt1->Pt) && + (j->OffPt == j->OutPt2->Pt)) + { + //Strictly Simple join ... + if (outRec1 != outRec2) return false; + op1b = j->OutPt1->Next; + while (op1b != op1 && (op1b->Pt == j->OffPt)) + op1b = op1b->Next; + bool reverse1 = (op1b->Pt.Y > j->OffPt.Y); + op2b = j->OutPt2->Next; + while (op2b != op2 && (op2b->Pt == j->OffPt)) + op2b = op2b->Next; + bool reverse2 = (op2b->Pt.Y > j->OffPt.Y); + if (reverse1 == reverse2) return false; + if (reverse1) + { + op1b = DupOutPt(op1, false); + op2b = DupOutPt(op2, true); + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } else + { + op1b = DupOutPt(op1, true); + op2b = DupOutPt(op2, false); + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } + } + else if (isHorizontal) + { + //treat horizontal joins differently to non-horizontal joins since with + //them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt + //may be anywhere along the horizontal edge. + op1b = op1; + while (op1->Prev->Pt.Y == op1->Pt.Y && op1->Prev != op1b && op1->Prev != op2) + op1 = op1->Prev; + while (op1b->Next->Pt.Y == op1b->Pt.Y && op1b->Next != op1 && op1b->Next != op2) + op1b = op1b->Next; + if (op1b->Next == op1 || op1b->Next == op2) return false; //a flat 'polygon' + + op2b = op2; + while (op2->Prev->Pt.Y == op2->Pt.Y && op2->Prev != op2b && op2->Prev != op1b) + op2 = op2->Prev; + while (op2b->Next->Pt.Y == op2b->Pt.Y && op2b->Next != op2 && op2b->Next != op1) + op2b = op2b->Next; + if (op2b->Next == op2 || op2b->Next == op1) return false; //a flat 'polygon' + + cInt Left, Right; + //Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges + if (!GetOverlap(op1->Pt.X, op1b->Pt.X, op2->Pt.X, op2b->Pt.X, Left, Right)) + return false; + + //DiscardLeftSide: when overlapping edges are joined, a spike will created + //which needs to be cleaned up. However, we don't want Op1 or Op2 caught up + //on the discard Side as either may still be needed for other joins ... + IntPoint Pt; + bool DiscardLeftSide; + if (op1->Pt.X >= Left && op1->Pt.X <= Right) + { + Pt = op1->Pt; DiscardLeftSide = (op1->Pt.X > op1b->Pt.X); + } + else if (op2->Pt.X >= Left&& op2->Pt.X <= Right) + { + Pt = op2->Pt; DiscardLeftSide = (op2->Pt.X > op2b->Pt.X); + } + else if (op1b->Pt.X >= Left && op1b->Pt.X <= Right) + { + Pt = op1b->Pt; DiscardLeftSide = op1b->Pt.X > op1->Pt.X; + } + else + { + Pt = op2b->Pt; DiscardLeftSide = (op2b->Pt.X > op2->Pt.X); + } + j->OutPt1 = op1; j->OutPt2 = op2; + return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide); + } else + { + //nb: For non-horizontal joins ... + // 1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y + // 2. Jr.OutPt1.Pt > Jr.OffPt.Y + + //make sure the polygons are correctly oriented ... + op1b = op1->Next; + while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Next; + bool Reverse1 = ((op1b->Pt.Y > op1->Pt.Y) || + !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)); + if (Reverse1) + { + op1b = op1->Prev; + while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Prev; + if ((op1b->Pt.Y > op1->Pt.Y) || + !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)) return false; + }; + op2b = op2->Next; + while ((op2b->Pt == op2->Pt) && (op2b != op2))op2b = op2b->Next; + bool Reverse2 = ((op2b->Pt.Y > op2->Pt.Y) || + !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)); + if (Reverse2) + { + op2b = op2->Prev; + while ((op2b->Pt == op2->Pt) && (op2b != op2)) op2b = op2b->Prev; + if ((op2b->Pt.Y > op2->Pt.Y) || + !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)) return false; + } + + if ((op1b == op1) || (op2b == op2) || (op1b == op2b) || + ((outRec1 == outRec2) && (Reverse1 == Reverse2))) return false; + + if (Reverse1) + { + op1b = DupOutPt(op1, false); + op2b = DupOutPt(op2, true); + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } else + { + op1b = DupOutPt(op1, true); + op2b = DupOutPt(op2, false); + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } + } +} +//---------------------------------------------------------------------- + +static OutRec* ParseFirstLeft(OutRec* FirstLeft) +{ + while (FirstLeft && !FirstLeft->Pts) + FirstLeft = FirstLeft->FirstLeft; + return FirstLeft; +} +//------------------------------------------------------------------------------ + +void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) +{ + //tests if NewOutRec contains the polygon before reassigning FirstLeft + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec* outRec = m_PolyOuts[i]; + OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft); + if (outRec->Pts && firstLeft == OldOutRec) + { + if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts)) + outRec->FirstLeft = NewOutRec; + } + } +} +//---------------------------------------------------------------------- + +void Clipper::FixupFirstLefts2(OutRec* InnerOutRec, OutRec* OuterOutRec) +{ + //A polygon has split into two such that one is now the inner of the other. + //It's possible that these polygons now wrap around other polygons, so check + //every polygon that's also contained by OuterOutRec's FirstLeft container + //(including 0) to see if they've become inner to the new inner polygon ... + OutRec* orfl = OuterOutRec->FirstLeft; + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec* outRec = m_PolyOuts[i]; + + if (!outRec->Pts || outRec == OuterOutRec || outRec == InnerOutRec) + continue; + OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft); + if (firstLeft != orfl && firstLeft != InnerOutRec && firstLeft != OuterOutRec) + continue; + if (Poly2ContainsPoly1(outRec->Pts, InnerOutRec->Pts)) + outRec->FirstLeft = InnerOutRec; + else if (Poly2ContainsPoly1(outRec->Pts, OuterOutRec->Pts)) + outRec->FirstLeft = OuterOutRec; + else if (outRec->FirstLeft == InnerOutRec || outRec->FirstLeft == OuterOutRec) + outRec->FirstLeft = orfl; + } +} +//---------------------------------------------------------------------- +void Clipper::FixupFirstLefts3(OutRec* OldOutRec, OutRec* NewOutRec) +{ + //reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec* outRec = m_PolyOuts[i]; + OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft); + if (outRec->Pts && outRec->FirstLeft == OldOutRec) + outRec->FirstLeft = NewOutRec; + } +} +//---------------------------------------------------------------------- + +void Clipper::JoinCommonEdges() +{ + for (JoinList::size_type i = 0; i < m_Joins.size(); i++) + { + Join* join = m_Joins[i]; + + OutRec *outRec1 = GetOutRec(join->OutPt1->Idx); + OutRec *outRec2 = GetOutRec(join->OutPt2->Idx); + + if (!outRec1->Pts || !outRec2->Pts) continue; + if (outRec1->IsOpen || outRec2->IsOpen) continue; + + //get the polygon fragment with the correct hole state (FirstLeft) + //before calling JoinPoints() ... + OutRec *holeStateRec; + if (outRec1 == outRec2) holeStateRec = outRec1; + else if (OutRec1RightOfOutRec2(outRec1, outRec2)) holeStateRec = outRec2; + else if (OutRec1RightOfOutRec2(outRec2, outRec1)) holeStateRec = outRec1; + else holeStateRec = GetLowermostRec(outRec1, outRec2); + + if (!JoinPoints(join, outRec1, outRec2)) continue; + + if (outRec1 == outRec2) + { + //instead of joining two polygons, we've just created a new one by + //splitting one polygon into two. + outRec1->Pts = join->OutPt1; + outRec1->BottomPt = 0; + outRec2 = CreateOutRec(); + outRec2->Pts = join->OutPt2; + + //update all OutRec2.Pts Idx's ... + UpdateOutPtIdxs(*outRec2); + + if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts)) + { + //outRec1 contains outRec2 ... + outRec2->IsHole = !outRec1->IsHole; + outRec2->FirstLeft = outRec1; + + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + + if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0)) + ReversePolyPtLinks(outRec2->Pts); + + } else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts)) + { + //outRec2 contains outRec1 ... + outRec2->IsHole = outRec1->IsHole; + outRec1->IsHole = !outRec2->IsHole; + outRec2->FirstLeft = outRec1->FirstLeft; + outRec1->FirstLeft = outRec2; + + if (m_UsingPolyTree) FixupFirstLefts2(outRec1, outRec2); + + if ((outRec1->IsHole ^ m_ReverseOutput) == (Area(*outRec1) > 0)) + ReversePolyPtLinks(outRec1->Pts); + } + else + { + //the 2 polygons are completely separate ... + outRec2->IsHole = outRec1->IsHole; + outRec2->FirstLeft = outRec1->FirstLeft; + + //fixup FirstLeft pointers that may need reassigning to OutRec2 + if (m_UsingPolyTree) FixupFirstLefts1(outRec1, outRec2); + } + + } else + { + //joined 2 polygons together ... + + outRec2->Pts = 0; + outRec2->BottomPt = 0; + outRec2->Idx = outRec1->Idx; + + outRec1->IsHole = holeStateRec->IsHole; + if (holeStateRec == outRec2) + outRec1->FirstLeft = outRec2->FirstLeft; + outRec2->FirstLeft = outRec1; + + if (m_UsingPolyTree) FixupFirstLefts3(outRec2, outRec1); + } + } +} + +//------------------------------------------------------------------------------ +// ClipperOffset support functions ... +//------------------------------------------------------------------------------ + +DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) +{ + if(pt2.X == pt1.X && pt2.Y == pt1.Y) + return DoublePoint(0, 0); + + double Dx = (double)(pt2.X - pt1.X); + double dy = (double)(pt2.Y - pt1.Y); + double f = 1 *1.0/ std::sqrt( Dx*Dx + dy*dy ); + Dx *= f; + dy *= f; + return DoublePoint(dy, -Dx); +} + +//------------------------------------------------------------------------------ +// ClipperOffset class +//------------------------------------------------------------------------------ + +ClipperOffset::ClipperOffset(double miterLimit, double arcTolerance) +{ + this->MiterLimit = miterLimit; + this->ArcTolerance = arcTolerance; + m_lowest.X = -1; +} +//------------------------------------------------------------------------------ + +ClipperOffset::~ClipperOffset() +{ + Clear(); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::Clear() +{ + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) + delete m_polyNodes.Childs[i]; + m_polyNodes.Childs.clear(); + m_lowest.X = -1; +} +//------------------------------------------------------------------------------ + +void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType) +{ + int highI = (int)path.size() - 1; + if (highI < 0) return; + PolyNode* newNode = new PolyNode(); + newNode->m_jointype = joinType; + newNode->m_endtype = endType; + + //strip duplicate points from path and also get index to the lowest point ... + if (endType == etClosedLine || endType == etClosedPolygon) + while (highI > 0 && path[0] == path[highI]) highI--; + newNode->Contour.reserve(highI + 1); + newNode->Contour.push_back(path[0]); + int j = 0, k = 0; + for (int i = 1; i <= highI; i++) + if (newNode->Contour[j] != path[i]) + { + j++; + newNode->Contour.push_back(path[i]); + if (path[i].Y > newNode->Contour[k].Y || + (path[i].Y == newNode->Contour[k].Y && + path[i].X < newNode->Contour[k].X)) k = j; + } + if (endType == etClosedPolygon && j < 2) + { + delete newNode; + return; + } + m_polyNodes.AddChild(*newNode); + + //if this path's lowest pt is lower than all the others then update m_lowest + if (endType != etClosedPolygon) return; + if (m_lowest.X < 0) + m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); + else + { + IntPoint ip = m_polyNodes.Childs[(int)m_lowest.X]->Contour[(int)m_lowest.Y]; + if (newNode->Contour[k].Y > ip.Y || + (newNode->Contour[k].Y == ip.Y && + newNode->Contour[k].X < ip.X)) + m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::AddPaths(const Paths& paths, JoinType joinType, EndType endType) +{ + for (Paths::size_type i = 0; i < paths.size(); ++i) + AddPath(paths[i], joinType, endType); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::FixOrientations() +{ + //fixup orientations of all closed paths if the orientation of the + //closed path with the lowermost vertex is wrong ... + if (m_lowest.X >= 0 && + !Orientation(m_polyNodes.Childs[(int)m_lowest.X]->Contour)) + { + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) + { + PolyNode& node = *m_polyNodes.Childs[i]; + if (node.m_endtype == etClosedPolygon || + (node.m_endtype == etClosedLine && Orientation(node.Contour))) + ReversePath(node.Contour); + } + } else + { + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) + { + PolyNode& node = *m_polyNodes.Childs[i]; + if (node.m_endtype == etClosedLine && !Orientation(node.Contour)) + ReversePath(node.Contour); + } + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::Execute(Paths& solution, double delta) +{ + solution.clear(); + FixOrientations(); + DoOffset(delta); + + //now clean up 'corners' ... + Clipper clpr; + clpr.AddPaths(m_destPolys, ptSubject, true); + if (delta > 0) + { + clpr.Execute(ctUnion, solution, pftPositive, pftPositive); + } + else + { + IntRect r = clpr.GetBounds(); + Path outer(4); + outer[0] = IntPoint(r.left - 10, r.bottom + 10); + outer[1] = IntPoint(r.right + 10, r.bottom + 10); + outer[2] = IntPoint(r.right + 10, r.top - 10); + outer[3] = IntPoint(r.left - 10, r.top - 10); + + clpr.AddPath(outer, ptSubject, true); + clpr.ReverseSolution(true); + clpr.Execute(ctUnion, solution, pftNegative, pftNegative); + if (solution.size() > 0) solution.erase(solution.begin()); + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::Execute(PolyTree& solution, double delta) +{ + solution.Clear(); + FixOrientations(); + DoOffset(delta); + + //now clean up 'corners' ... + Clipper clpr; + clpr.AddPaths(m_destPolys, ptSubject, true); + if (delta > 0) + { + clpr.Execute(ctUnion, solution, pftPositive, pftPositive); + } + else + { + IntRect r = clpr.GetBounds(); + Path outer(4); + outer[0] = IntPoint(r.left - 10, r.bottom + 10); + outer[1] = IntPoint(r.right + 10, r.bottom + 10); + outer[2] = IntPoint(r.right + 10, r.top - 10); + outer[3] = IntPoint(r.left - 10, r.top - 10); + + clpr.AddPath(outer, ptSubject, true); + clpr.ReverseSolution(true); + clpr.Execute(ctUnion, solution, pftNegative, pftNegative); + //remove the outer PolyNode rectangle ... + if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0) + { + PolyNode* outerNode = solution.Childs[0]; + solution.Childs.reserve(outerNode->ChildCount()); + solution.Childs[0] = outerNode->Childs[0]; + solution.Childs[0]->Parent = outerNode->Parent; + for (int i = 1; i < outerNode->ChildCount(); ++i) + solution.AddChild(*outerNode->Childs[i]); + } + else + solution.Clear(); + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoOffset(double delta) +{ + m_destPolys.clear(); + m_delta = delta; + + //if Zero offset, just copy any CLOSED polygons to m_p and return ... + if (NEAR_ZERO(delta)) + { + m_destPolys.reserve(m_polyNodes.ChildCount()); + for (int i = 0; i < m_polyNodes.ChildCount(); i++) + { + PolyNode& node = *m_polyNodes.Childs[i]; + if (node.m_endtype == etClosedPolygon) + m_destPolys.push_back(node.Contour); + } + return; + } + + //see offset_triginometry3.svg in the documentation folder ... + if (MiterLimit > 2) m_miterLim = 2/(MiterLimit * MiterLimit); + else m_miterLim = 0.5; + + double y; + if (ArcTolerance <= 0.0) y = def_arc_tolerance; + else if (ArcTolerance > std::fabs(delta) * def_arc_tolerance) + y = std::fabs(delta) * def_arc_tolerance; + else y = ArcTolerance; + //see offset_triginometry2.svg in the documentation folder ... + double steps = pi / std::acos(1 - y / std::fabs(delta)); + if (steps > std::fabs(delta) * pi) + steps = std::fabs(delta) * pi; //ie excessive precision check + m_sin = std::sin(two_pi / steps); + m_cos = std::cos(two_pi / steps); + m_StepsPerRad = steps / two_pi; + if (delta < 0.0) m_sin = -m_sin; + + m_destPolys.reserve(m_polyNodes.ChildCount() * 2); + for (int i = 0; i < m_polyNodes.ChildCount(); i++) + { + PolyNode& node = *m_polyNodes.Childs[i]; + m_srcPoly = node.Contour; + + int len = (int)m_srcPoly.size(); + if (len == 0 || (delta <= 0 && (len < 3 || node.m_endtype != etClosedPolygon))) + continue; + + m_destPoly.clear(); + if (len == 1) + { + if (node.m_jointype == jtRound) + { + double X = 1.0, Y = 0.0; + for (cInt j = 1; j <= steps; j++) + { + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[0].X + X * delta), + Round(m_srcPoly[0].Y + Y * delta))); + double X2 = X; + X = X * m_cos - m_sin * Y; + Y = X2 * m_sin + Y * m_cos; + } + } + else + { + double X = -1.0, Y = -1.0; + for (int j = 0; j < 4; ++j) + { + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[0].X + X * delta), + Round(m_srcPoly[0].Y + Y * delta))); + if (X < 0) X = 1; + else if (Y < 0) Y = 1; + else X = -1; + } + } + m_destPolys.push_back(m_destPoly); + continue; + } + //build m_normals ... + m_normals.clear(); + m_normals.reserve(len); + for (int j = 0; j < len - 1; ++j) + m_normals.push_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1])); + if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon) + m_normals.push_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0])); + else + m_normals.push_back(DoublePoint(m_normals[len - 2])); + + if (node.m_endtype == etClosedPolygon) + { + int k = len - 1; + for (int j = 0; j < len; ++j) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.push_back(m_destPoly); + } + else if (node.m_endtype == etClosedLine) + { + int k = len - 1; + for (int j = 0; j < len; ++j) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.push_back(m_destPoly); + m_destPoly.clear(); + //re-build m_normals ... + DoublePoint n = m_normals[len -1]; + for (int j = len - 1; j > 0; j--) + m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); + m_normals[0] = DoublePoint(-n.X, -n.Y); + k = 0; + for (int j = len - 1; j >= 0; j--) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.push_back(m_destPoly); + } + else + { + int k = 0; + for (int j = 1; j < len - 1; ++j) + OffsetPoint(j, k, node.m_jointype); + + IntPoint pt1; + if (node.m_endtype == etOpenButt) + { + int j = len - 1; + pt1 = IntPoint((cInt)Round(m_srcPoly[j].X + m_normals[j].X * + delta), (cInt)Round(m_srcPoly[j].Y + m_normals[j].Y * delta)); + m_destPoly.push_back(pt1); + pt1 = IntPoint((cInt)Round(m_srcPoly[j].X - m_normals[j].X * + delta), (cInt)Round(m_srcPoly[j].Y - m_normals[j].Y * delta)); + m_destPoly.push_back(pt1); + } + else + { + int j = len - 1; + k = len - 2; + m_sinA = 0; + m_normals[j] = DoublePoint(-m_normals[j].X, -m_normals[j].Y); + if (node.m_endtype == etOpenSquare) + DoSquare(j, k); + else + DoRound(j, k); + } + + //re-build m_normals ... + for (int j = len - 1; j > 0; j--) + m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); + m_normals[0] = DoublePoint(-m_normals[1].X, -m_normals[1].Y); + + k = len - 1; + for (int j = k - 1; j > 0; --j) OffsetPoint(j, k, node.m_jointype); + + if (node.m_endtype == etOpenButt) + { + pt1 = IntPoint((cInt)Round(m_srcPoly[0].X - m_normals[0].X * delta), + (cInt)Round(m_srcPoly[0].Y - m_normals[0].Y * delta)); + m_destPoly.push_back(pt1); + pt1 = IntPoint((cInt)Round(m_srcPoly[0].X + m_normals[0].X * delta), + (cInt)Round(m_srcPoly[0].Y + m_normals[0].Y * delta)); + m_destPoly.push_back(pt1); + } + else + { + k = 1; + m_sinA = 0; + if (node.m_endtype == etOpenSquare) + DoSquare(0, 1); + else + DoRound(0, 1); + } + m_destPolys.push_back(m_destPoly); + } + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype) +{ + //cross product ... + m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y); + if (std::fabs(m_sinA * m_delta) < 1.0) + { + //dot product ... + double cosA = (m_normals[k].X * m_normals[j].X + m_normals[j].Y * m_normals[k].Y ); + if (cosA > 0) // angle => 0 degrees + { + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); + return; + } + //else angle => 180 degrees + } + else if (m_sinA > 1.0) m_sinA = 1.0; + else if (m_sinA < -1.0) m_sinA = -1.0; + + if (m_sinA * m_delta < 0) + { + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); + m_destPoly.push_back(m_srcPoly[j]); + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); + } + else + switch (jointype) + { + case jtMiter: + { + double r = 1 + (m_normals[j].X * m_normals[k].X + + m_normals[j].Y * m_normals[k].Y); + if (r >= m_miterLim) DoMiter(j, k, r); else DoSquare(j, k); + break; + } + case jtSquare: DoSquare(j, k); break; + case jtRound: DoRound(j, k); break; + } + k = j; +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoSquare(int j, int k) +{ + double dx = std::tan(std::atan2(m_sinA, + m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y) / 4); + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)), + Round(m_srcPoly[j].Y + m_delta * (m_normals[k].Y + m_normals[k].X * dx)))); + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)), + Round(m_srcPoly[j].Y + m_delta * (m_normals[j].Y - m_normals[j].X * dx)))); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoMiter(int j, int k, double r) +{ + double q = m_delta / r; + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q), + Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q))); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoRound(int j, int k) +{ + double a = std::atan2(m_sinA, + m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y); + int steps = std::max((int)Round(m_StepsPerRad * std::fabs(a)), 1); + + double X = m_normals[k].X, Y = m_normals[k].Y, X2; + for (int i = 0; i < steps; ++i) + { + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + X * m_delta), + Round(m_srcPoly[j].Y + Y * m_delta))); + X2 = X; + X = X * m_cos - m_sin * Y; + Y = X2 * m_sin + Y * m_cos; + } + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + m_normals[j].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); +} + +//------------------------------------------------------------------------------ +// Miscellaneous public functions +//------------------------------------------------------------------------------ + +void Clipper::DoSimplePolygons() +{ + PolyOutList::size_type i = 0; + while (i < m_PolyOuts.size()) + { + OutRec* outrec = m_PolyOuts[i++]; + OutPt* op = outrec->Pts; + if (!op || outrec->IsOpen) continue; + do //for each Pt in Polygon until duplicate found do ... + { + OutPt* op2 = op->Next; + while (op2 != outrec->Pts) + { + if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op) + { + //split the polygon into two ... + OutPt* op3 = op->Prev; + OutPt* op4 = op2->Prev; + op->Prev = op4; + op4->Next = op; + op2->Prev = op3; + op3->Next = op2; + + outrec->Pts = op; + OutRec* outrec2 = CreateOutRec(); + outrec2->Pts = op2; + UpdateOutPtIdxs(*outrec2); + if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts)) + { + //OutRec2 is contained by OutRec1 ... + outrec2->IsHole = !outrec->IsHole; + outrec2->FirstLeft = outrec; + if (m_UsingPolyTree) FixupFirstLefts2(outrec2, outrec); + } + else + if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts)) + { + //OutRec1 is contained by OutRec2 ... + outrec2->IsHole = outrec->IsHole; + outrec->IsHole = !outrec2->IsHole; + outrec2->FirstLeft = outrec->FirstLeft; + outrec->FirstLeft = outrec2; + if (m_UsingPolyTree) FixupFirstLefts2(outrec, outrec2); + } + else + { + //the 2 polygons are separate ... + outrec2->IsHole = outrec->IsHole; + outrec2->FirstLeft = outrec->FirstLeft; + if (m_UsingPolyTree) FixupFirstLefts1(outrec, outrec2); + } + op2 = op; //ie get ready for the Next iteration + } + op2 = op2->Next; + } + op = op->Next; + } + while (op != outrec->Pts); + } +} +//------------------------------------------------------------------------------ + +void ReversePath(Path& p) +{ + std::reverse(p.begin(), p.end()); +} +//------------------------------------------------------------------------------ + +void ReversePaths(Paths& p) +{ + for (Paths::size_type i = 0; i < p.size(); ++i) + ReversePath(p[i]); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType) +{ + Clipper c; + c.StrictlySimple(true); + c.AddPath(in_poly, ptSubject, true); + c.Execute(ctUnion, out_polys, fillType, fillType); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType) +{ + Clipper c; + c.StrictlySimple(true); + c.AddPaths(in_polys, ptSubject, true); + c.Execute(ctUnion, out_polys, fillType, fillType); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygons(Paths &polys, PolyFillType fillType) +{ + SimplifyPolygons(polys, polys, fillType); +} +//------------------------------------------------------------------------------ + +inline double DistanceSqrd(const IntPoint& pt1, const IntPoint& pt2) +{ + double Dx = ((double)pt1.X - pt2.X); + double dy = ((double)pt1.Y - pt2.Y); + return (Dx*Dx + dy*dy); +} +//------------------------------------------------------------------------------ + +double DistanceFromLineSqrd( + const IntPoint& pt, const IntPoint& ln1, const IntPoint& ln2) +{ + //The equation of a line in general form (Ax + By + C = 0) + //given 2 points (x¹,y¹) & (x²,y²) is ... + //(y¹ - y²)x + (x² - x¹)y + (y² - y¹)x¹ - (x² - x¹)y¹ = 0 + //A = (y¹ - y²); B = (x² - x¹); C = (y² - y¹)x¹ - (x² - x¹)y¹ + //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²) + //see http://en.wikipedia.org/wiki/Perpendicular_distance + double A = double(ln1.Y - ln2.Y); + double B = double(ln2.X - ln1.X); + double C = A * ln1.X + B * ln1.Y; + C = A * pt.X + B * pt.Y - C; + return (C * C) / (A * A + B * B); +} +//--------------------------------------------------------------------------- + +bool SlopesNearCollinear(const IntPoint& pt1, + const IntPoint& pt2, const IntPoint& pt3, double distSqrd) +{ + //this function is more accurate when the point that's geometrically + //between the other 2 points is the one that's tested for distance. + //ie makes it more likely to pick up 'spikes' ... + if (Abs(pt1.X - pt2.X) > Abs(pt1.Y - pt2.Y)) + { + if ((pt1.X > pt2.X) == (pt1.X < pt3.X)) + return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; + else if ((pt2.X > pt1.X) == (pt2.X < pt3.X)) + return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; + else + return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; + } + else + { + if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y)) + return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; + else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y)) + return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; + else + return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; + } +} +//------------------------------------------------------------------------------ + +bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) +{ + double Dx = (double)pt1.X - pt2.X; + double dy = (double)pt1.Y - pt2.Y; + return ((Dx * Dx) + (dy * dy) <= distSqrd); +} +//------------------------------------------------------------------------------ + +OutPt* ExcludeOp(OutPt* op) +{ + OutPt* result = op->Prev; + result->Next = op->Next; + op->Next->Prev = result; + result->Idx = 0; + return result; +} +//------------------------------------------------------------------------------ + +void CleanPolygon(const Path& in_poly, Path& out_poly, double distance) +{ + //distance = proximity in units/pixels below which vertices + //will be stripped. Default ~= sqrt(2). + + size_t size = in_poly.size(); + + if (size == 0) + { + out_poly.clear(); + return; + } + + OutPt* outPts = new OutPt[size]; + for (size_t i = 0; i < size; ++i) + { + outPts[i].Pt = in_poly[i]; + outPts[i].Next = &outPts[(i + 1) % size]; + outPts[i].Next->Prev = &outPts[i]; + outPts[i].Idx = 0; + } + + double distSqrd = distance * distance; + OutPt* op = &outPts[0]; + while (op->Idx == 0 && op->Next != op->Prev) + { + if (PointsAreClose(op->Pt, op->Prev->Pt, distSqrd)) + { + op = ExcludeOp(op); + size--; + } + else if (PointsAreClose(op->Prev->Pt, op->Next->Pt, distSqrd)) + { + ExcludeOp(op->Next); + op = ExcludeOp(op); + size -= 2; + } + else if (SlopesNearCollinear(op->Prev->Pt, op->Pt, op->Next->Pt, distSqrd)) + { + op = ExcludeOp(op); + size--; + } + else + { + op->Idx = 1; + op = op->Next; + } + } + + if (size < 3) size = 0; + out_poly.resize(size); + for (size_t i = 0; i < size; ++i) + { + out_poly[i] = op->Pt; + op = op->Next; + } + delete [] outPts; +} +//------------------------------------------------------------------------------ + +void CleanPolygon(Path& poly, double distance) +{ + CleanPolygon(poly, poly, distance); +} +//------------------------------------------------------------------------------ + +void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance) +{ + out_polys.resize(in_polys.size()); + for (Paths::size_type i = 0; i < in_polys.size(); ++i) + CleanPolygon(in_polys[i], out_polys[i], distance); +} +//------------------------------------------------------------------------------ + +void CleanPolygons(Paths& polys, double distance) +{ + CleanPolygons(polys, polys, distance); +} +//------------------------------------------------------------------------------ + +void Minkowski(const Path& poly, const Path& path, + Paths& solution, bool isSum, bool isClosed) +{ + int delta = (isClosed ? 1 : 0); + size_t polyCnt = poly.size(); + size_t pathCnt = path.size(); + Paths pp; + pp.reserve(pathCnt); + if (isSum) + for (size_t i = 0; i < pathCnt; ++i) + { + Path p; + p.reserve(polyCnt); + for (size_t j = 0; j < poly.size(); ++j) + p.push_back(IntPoint(path[i].X + poly[j].X, path[i].Y + poly[j].Y)); + pp.push_back(p); + } + else + for (size_t i = 0; i < pathCnt; ++i) + { + Path p; + p.reserve(polyCnt); + for (size_t j = 0; j < poly.size(); ++j) + p.push_back(IntPoint(path[i].X - poly[j].X, path[i].Y - poly[j].Y)); + pp.push_back(p); + } + + solution.clear(); + solution.reserve((pathCnt + delta) * (polyCnt + 1)); + for (size_t i = 0; i < pathCnt - 1 + delta; ++i) + for (size_t j = 0; j < polyCnt; ++j) + { + Path quad; + quad.reserve(4); + quad.push_back(pp[i % pathCnt][j % polyCnt]); + quad.push_back(pp[(i + 1) % pathCnt][j % polyCnt]); + quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]); + quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]); + if (!Orientation(quad)) ReversePath(quad); + solution.push_back(quad); + } +} +//------------------------------------------------------------------------------ + +void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed) +{ + Minkowski(pattern, path, solution, true, pathIsClosed); + Clipper c; + c.AddPaths(solution, ptSubject, true); + c.Execute(ctUnion, solution, pftNonZero, pftNonZero); +} +//------------------------------------------------------------------------------ + +void TranslatePath(const Path& input, Path& output, const IntPoint delta) +{ + //precondition: input != output + output.resize(input.size()); + for (size_t i = 0; i < input.size(); ++i) + output[i] = IntPoint(input[i].X + delta.X, input[i].Y + delta.Y); +} +//------------------------------------------------------------------------------ + +void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed) +{ + Clipper c; + for (size_t i = 0; i < paths.size(); ++i) + { + Paths tmp; + Minkowski(pattern, paths[i], tmp, true, pathIsClosed); + c.AddPaths(tmp, ptSubject, true); + if (pathIsClosed) + { + Path tmp2; + TranslatePath(paths[i], tmp2, pattern[0]); + c.AddPath(tmp2, ptClip, true); + } + } + c.Execute(ctUnion, solution, pftNonZero, pftNonZero); +} +//------------------------------------------------------------------------------ + +void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution) +{ + Minkowski(poly1, poly2, solution, false, true); + Clipper c; + c.AddPaths(solution, ptSubject, true); + c.Execute(ctUnion, solution, pftNonZero, pftNonZero); +} +//------------------------------------------------------------------------------ + +enum NodeType {ntAny, ntOpen, ntClosed}; + +void AddPolyNodeToPaths(const PolyNode& polynode, NodeType nodetype, Paths& paths) +{ + bool match = true; + if (nodetype == ntClosed) match = !polynode.IsOpen(); + else if (nodetype == ntOpen) return; + + if (!polynode.Contour.empty() && match) + paths.push_back(polynode.Contour); + for (int i = 0; i < polynode.ChildCount(); ++i) + AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths); +} +//------------------------------------------------------------------------------ + +void PolyTreeToPaths(const PolyTree& polytree, Paths& paths) +{ + paths.resize(0); + paths.reserve(polytree.Total()); + AddPolyNodeToPaths(polytree, ntAny, paths); +} +//------------------------------------------------------------------------------ + +void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths) +{ + paths.resize(0); + paths.reserve(polytree.Total()); + AddPolyNodeToPaths(polytree, ntClosed, paths); +} +//------------------------------------------------------------------------------ + +void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths) +{ + paths.resize(0); + paths.reserve(polytree.Total()); + //Open paths are top level only, so ... + for (int i = 0; i < polytree.ChildCount(); ++i) + if (polytree.Childs[i]->IsOpen()) + paths.push_back(polytree.Childs[i]->Contour); +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, const IntPoint &p) +{ + s << "(" << p.X << "," << p.Y << ")"; + return s; +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, const Path &p) +{ + if (p.empty()) return s; + Path::size_type last = p.size() -1; + for (Path::size_type i = 0; i < last; i++) + s << "(" << p[i].X << "," << p[i].Y << "), "; + s << "(" << p[last].X << "," << p[last].Y << ")\n"; + return s; +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, const Paths &p) +{ + for (Paths::size_type i = 0; i < p.size(); i++) + s << p[i]; + s << "\n"; + return s; +} +//------------------------------------------------------------------------------ + +} //ClipperLib namespace diff --git a/clipper/clipper.hpp b/clipper/clipper.hpp new file mode 100644 index 000000000..c5bcacc0a --- /dev/null +++ b/clipper/clipper.hpp @@ -0,0 +1,404 @@ +/******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 6.4.0 * +* Date : 2 July 2015 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2015 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +* Attributions: * +* The code in this library is an extension of Bala Vatti's clipping algorithm: * +* "A generic solution to polygon clipping" * +* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * +* http://portal.acm.org/citation.cfm?id=129906 * +* * +* Computer graphics and geometric modeling: implementation and algorithms * +* By Max K. Agoston * +* Springer; 1 edition (January 4, 2005) * +* http://books.google.com/books?q=vatti+clipping+agoston * +* * +* See also: * +* "Polygon Offsetting by Computing Winding Numbers" * +* Paper no. DETC2005-85513 pp. 565-575 * +* ASME 2005 International Design Engineering Technical Conferences * +* and Computers and Information in Engineering Conference (IDETC/CIE2005) * +* September 24-28, 2005 , Long Beach, California, USA * +* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * +* * +*******************************************************************************/ + +#ifndef clipper_hpp +#define clipper_hpp + +#define CLIPPER_VERSION "6.2.6" + +//use_int32: When enabled 32bit ints are used instead of 64bit ints. This +//improve performance but coordinate values are limited to the range +/- 46340 +//#define use_int32 + +//use_xyz: adds a Z member to IntPoint. Adds a minor cost to perfomance. +//#define use_xyz + +//use_lines: Enables line clipping. Adds a very minor cost to performance. +#define use_lines + +//use_deprecated: Enables temporary support for the obsolete functions +//#define use_deprecated + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ClipperLib { + +enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; +enum PolyType { ptSubject, ptClip }; +//By far the most widely used winding rules for polygon filling are +//EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) +//Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) +//see http://glprogramming.com/red/chapter11.html +enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; + +#ifdef use_int32 + typedef int cInt; + static cInt const loRange = 0x7FFF; + static cInt const hiRange = 0x7FFF; +#else + typedef signed long long cInt; + static cInt const loRange = 0x3FFFFFFF; + static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; + typedef signed long long long64; //used by Int128 class + typedef unsigned long long ulong64; + +#endif + +struct IntPoint { + cInt X; + cInt Y; +#ifdef use_xyz + cInt Z; + IntPoint(cInt x = 0, cInt y = 0, cInt z = 0): X(x), Y(y), Z(z) {}; +#else + IntPoint(cInt x = 0, cInt y = 0): X(x), Y(y) {}; +#endif + + friend inline bool operator== (const IntPoint& a, const IntPoint& b) + { + return a.X == b.X && a.Y == b.Y; + } + friend inline bool operator!= (const IntPoint& a, const IntPoint& b) + { + return a.X != b.X || a.Y != b.Y; + } +}; +//------------------------------------------------------------------------------ + +typedef std::vector< IntPoint > Path; +typedef std::vector< Path > Paths; + +inline Path& operator <<(Path& poly, const IntPoint& p) {poly.push_back(p); return poly;} +inline Paths& operator <<(Paths& polys, const Path& p) {polys.push_back(p); return polys;} + +std::ostream& operator <<(std::ostream &s, const IntPoint &p); +std::ostream& operator <<(std::ostream &s, const Path &p); +std::ostream& operator <<(std::ostream &s, const Paths &p); + +struct DoublePoint +{ + double X; + double Y; + DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} + DoublePoint(IntPoint ip) : X((double)ip.X), Y((double)ip.Y) {} +}; +//------------------------------------------------------------------------------ + +#ifdef use_xyz +typedef void (*ZFillCallback)(IntPoint& e1bot, IntPoint& e1top, IntPoint& e2bot, IntPoint& e2top, IntPoint& pt); +#endif + +enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4}; +enum JoinType {jtSquare, jtRound, jtMiter}; +enum EndType {etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound}; + +class PolyNode; +typedef std::vector< PolyNode* > PolyNodes; + +class PolyNode +{ +public: + PolyNode(); + virtual ~PolyNode(){}; + Path Contour; + PolyNodes Childs; + PolyNode* Parent; + PolyNode* GetNext() const; + bool IsHole() const; + bool IsOpen() const; + int ChildCount() const; +private: + unsigned Index; //node index in Parent.Childs + bool m_IsOpen; + JoinType m_jointype; + EndType m_endtype; + PolyNode* GetNextSiblingUp() const; + void AddChild(PolyNode& child); + friend class Clipper; //to access Index + friend class ClipperOffset; +}; + +class PolyTree: public PolyNode +{ +public: + ~PolyTree(){Clear();}; + PolyNode* GetFirst() const; + void Clear(); + int Total() const; +private: + PolyNodes AllNodes; + friend class Clipper; //to access AllNodes +}; + +bool Orientation(const Path &poly); +double Area(const Path &poly); +int PointInPolygon(const IntPoint &pt, const Path &path); + +void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType = pftEvenOdd); +void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType = pftEvenOdd); +void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd); + +void CleanPolygon(const Path& in_poly, Path& out_poly, double distance = 1.415); +void CleanPolygon(Path& poly, double distance = 1.415); +void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance = 1.415); +void CleanPolygons(Paths& polys, double distance = 1.415); + +void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed); +void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed); +void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution); + +void PolyTreeToPaths(const PolyTree& polytree, Paths& paths); +void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths); +void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths); + +void ReversePath(Path& p); +void ReversePaths(Paths& p); + +struct IntRect { cInt left; cInt top; cInt right; cInt bottom; }; + +//enums that are used internally ... +enum EdgeSide { esLeft = 1, esRight = 2}; + +//forward declarations (for stuff used internally) ... +struct TEdge; +struct IntersectNode; +struct LocalMinimum; +struct OutPt; +struct OutRec; +struct Join; + +typedef std::vector < OutRec* > PolyOutList; +typedef std::vector < TEdge* > EdgeList; +typedef std::vector < Join* > JoinList; +typedef std::vector < IntersectNode* > IntersectList; + +//------------------------------------------------------------------------------ + +//ClipperBase is the ancestor to the Clipper class. It should not be +//instantiated directly. This class simply abstracts the conversion of sets of +//polygon coordinates into edge objects that are stored in a LocalMinima list. +class ClipperBase +{ +public: + ClipperBase(); + virtual ~ClipperBase(); + virtual bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed); + bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed); + virtual void Clear(); + IntRect GetBounds(); + bool PreserveCollinear() {return m_PreserveCollinear;}; + void PreserveCollinear(bool value) {m_PreserveCollinear = value;}; +protected: + void DisposeLocalMinimaList(); + TEdge* AddBoundsToLML(TEdge *e, bool IsClosed); + virtual void Reset(); + TEdge* ProcessBound(TEdge* E, bool IsClockwise); + void InsertScanbeam(const cInt Y); + bool PopScanbeam(cInt &Y); + bool LocalMinimaPending(); + bool PopLocalMinima(cInt Y, const LocalMinimum *&locMin); + OutRec* CreateOutRec(); + void DisposeAllOutRecs(); + void DisposeOutRec(PolyOutList::size_type index); + void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); + void DeleteFromAEL(TEdge *e); + void UpdateEdgeIntoAEL(TEdge *&e); + + typedef std::vector MinimaList; + MinimaList::iterator m_CurrentLM; + MinimaList m_MinimaList; + + bool m_UseFullRange; + EdgeList m_edges; + bool m_PreserveCollinear; + bool m_HasOpenPaths; + PolyOutList m_PolyOuts; + TEdge *m_ActiveEdges; + + typedef std::priority_queue ScanbeamList; + ScanbeamList m_Scanbeam; +}; +//------------------------------------------------------------------------------ + +class Clipper : public virtual ClipperBase +{ +public: + Clipper(int initOptions = 0); + bool Execute(ClipType clipType, + Paths &solution, + PolyFillType fillType = pftEvenOdd); + bool Execute(ClipType clipType, + Paths &solution, + PolyFillType subjFillType, + PolyFillType clipFillType); + bool Execute(ClipType clipType, + PolyTree &polytree, + PolyFillType fillType = pftEvenOdd); + bool Execute(ClipType clipType, + PolyTree &polytree, + PolyFillType subjFillType, + PolyFillType clipFillType); + bool ReverseSolution() { return m_ReverseOutput; }; + void ReverseSolution(bool value) {m_ReverseOutput = value;}; + bool StrictlySimple() {return m_StrictSimple;}; + void StrictlySimple(bool value) {m_StrictSimple = value;}; + //set the callback function for z value filling on intersections (otherwise Z is 0) +#ifdef use_xyz + void ZFillFunction(ZFillCallback zFillFunc); +#endif +protected: + virtual bool ExecuteInternal(); +private: + JoinList m_Joins; + JoinList m_GhostJoins; + IntersectList m_IntersectList; + ClipType m_ClipType; + typedef std::list MaximaList; + MaximaList m_Maxima; + TEdge *m_SortedEdges; + bool m_ExecuteLocked; + PolyFillType m_ClipFillType; + PolyFillType m_SubjFillType; + bool m_ReverseOutput; + bool m_UsingPolyTree; + bool m_StrictSimple; +#ifdef use_xyz + ZFillCallback m_ZFill; //custom callback +#endif + void SetWindingCount(TEdge& edge); + bool IsEvenOddFillType(const TEdge& edge) const; + bool IsEvenOddAltFillType(const TEdge& edge) const; + void InsertLocalMinimaIntoAEL(const cInt botY); + void InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge); + void AddEdgeToSEL(TEdge *edge); + bool PopEdgeFromSEL(TEdge *&edge); + void CopyAELToSEL(); + void DeleteFromSEL(TEdge *e); + void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2); + bool IsContributing(const TEdge& edge) const; + bool IsTopHorz(const cInt XPos); + void DoMaxima(TEdge *e); + void ProcessHorizontals(); + void ProcessHorizontal(TEdge *horzEdge); + void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + OutRec* GetOutRec(int idx); + void AppendPolygon(TEdge *e1, TEdge *e2); + void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt); + OutPt* AddOutPt(TEdge *e, const IntPoint &pt); + OutPt* GetLastOutPt(TEdge *e); + bool ProcessIntersections(const cInt topY); + void BuildIntersectList(const cInt topY); + void ProcessIntersectList(); + void ProcessEdgesAtTopOfScanbeam(const cInt topY); + void BuildResult(Paths& polys); + void BuildResult2(PolyTree& polytree); + void SetHoleState(TEdge *e, OutRec *outrec); + void DisposeIntersectNodes(); + bool FixupIntersectionOrder(); + void FixupOutPolygon(OutRec &outrec); + void FixupOutPolyline(OutRec &outrec); + bool IsHole(TEdge *e); + bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl); + void FixHoleLinkage(OutRec &outrec); + void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt); + void ClearJoins(); + void ClearGhostJoins(); + void AddGhostJoin(OutPt *op, const IntPoint offPt); + bool JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2); + void JoinCommonEdges(); + void DoSimplePolygons(); + void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec); + void FixupFirstLefts2(OutRec* InnerOutRec, OutRec* OuterOutRec); + void FixupFirstLefts3(OutRec* OldOutRec, OutRec* NewOutRec); +#ifdef use_xyz + void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2); +#endif +}; +//------------------------------------------------------------------------------ + +class ClipperOffset +{ +public: + ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25); + ~ClipperOffset(); + void AddPath(const Path& path, JoinType joinType, EndType endType); + void AddPaths(const Paths& paths, JoinType joinType, EndType endType); + void Execute(Paths& solution, double delta); + void Execute(PolyTree& solution, double delta); + void Clear(); + double MiterLimit; + double ArcTolerance; +private: + Paths m_destPolys; + Path m_srcPoly; + Path m_destPoly; + std::vector m_normals; + double m_delta, m_sinA, m_sin, m_cos; + double m_miterLim, m_StepsPerRad; + IntPoint m_lowest; + PolyNode m_polyNodes; + + void FixOrientations(); + void DoOffset(double delta); + void OffsetPoint(int j, int& k, JoinType jointype); + void DoSquare(int j, int k); + void DoMiter(int j, int k, double r); + void DoRound(int j, int k); +}; +//------------------------------------------------------------------------------ + +class clipperException : public std::exception +{ + public: + clipperException(const char* description): m_descr(description) {} + virtual ~clipperException() throw() {} + virtual const char* what() const throw() {return m_descr.c_str();} + private: + std::string m_descr; +}; +//------------------------------------------------------------------------------ + +} //ClipperLib namespace + +#endif //clipper_hpp + + diff --git a/common/arc.cpp b/common/arc.cpp new file mode 100644 index 000000000..b908fd28c --- /dev/null +++ b/common/arc.cpp @@ -0,0 +1,34 @@ +#include "arc.hpp" +#include "lut.hpp" +#include "json.hpp" +#include "object.hpp" +#include + +namespace horizon { + + Arc::Arc(const UUID &uu, const json &j, Object &obj): + uuid(uu), + to(obj.get_junction(j["to"].get())), + from(obj.get_junction(j["from"].get())), + center(obj.get_junction(j["center"].get())), + width(j.value("width", 0)), + layer(j.value("layer", 0)) + { + } + + Arc::Arc(UUID uu): uuid(uu) {} + + void Arc::reverse() { + std::swap(to, from); + } + + json Arc::serialize() const { + json j; + j["from"] = (std::string)from.uuid; + j["to"] = (std::string)to.uuid; + j["center"] = (std::string)center.uuid; + j["width"] = width; + j["layer"] = layer; + return j; + } +} diff --git a/common/arc.hpp b/common/arc.hpp new file mode 100644 index 000000000..00a9c1477 --- /dev/null +++ b/common/arc.hpp @@ -0,0 +1,32 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "common.hpp" +#include "junction.hpp" +#include "object.hpp" +#include "position_provider.hpp" +#include "uuid_ptr.hpp" +#include +#include +#include + + +namespace horizon { + using json = nlohmann::json; + + + class Arc { + public : + Arc(const UUID &uu, const json &j, Object &obj); + Arc(UUID uu); + void reverse(); + + UUID uuid; + uuid_ptr to; + uuid_ptr from; + uuid_ptr center; + uint64_t width = 0; + int layer = 0; + json serialize() const; + }; +} diff --git a/common/common.hpp b/common/common.hpp new file mode 100644 index 000000000..e589f0ffd --- /dev/null +++ b/common/common.hpp @@ -0,0 +1,91 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace horizon { + enum class Orientation {LEFT, RIGHT, UP, DOWN}; + enum class ObjectType { + INVALID, JUNCTION, LINE, SYMBOL_PIN, ARC, SCHEMATIC_SYMBOL, + TEXT, LINE_NET, COMPONENT, NET, NET_LABEL, POWER_SYMBOL, BUS, + BUS_LABEL, BUS_RIPPER, POLYGON, POLYGON_VERTEX, POLYGON_EDGE, POLYGON_ARC_CENTER, + HOLE, PAD, BOARD_PACKAGE, TRACK, VIA + }; + + template class Coord { + public : + T x; + T y; + + //WTF, but works + //template + //Coord(double ix, double iy, typename std::enable_if::value>::type* = 0) : x((float)ix), y((float)iy) { } + + + Coord(T ix, T iy): x(ix), y(iy) {} + Coord(): x(0), y(0) {} + Coord(std::vector v): x(v.at(0)), y(v.at(1)) {} + operator Coord() const {return Coord(x, y);} + operator Coord() const {return Coord(x, y);} + Coord operator+ (const Coord &a) const {return Coord(x+a.x, y+a.y);} + Coord operator- (const Coord &a) const {return Coord(x-a.x, y-a.y);} + Coord operator* (const Coord &a) const {return Coord(x*a.x, y*a.y);} + Coord operator* (T r) const {return Coord(x*r, y*r);} + Coord operator/ (T r) const {return Coord(x/r, y/r);} + bool operator== (const Coord &a) const {return a.x == x && a.y==y;} + bool operator!= (const Coord &a) const {return !(a==*this);} + + static Coord min(const Coord &a, const Coord &b) { + return Coord(std::min(a.x, b.x), std::min(a.y, b.y)); + } + static Coord max(const Coord &a, const Coord &b) { + return Coord(std::max(a.x, b.x), std::max(a.y, b.y)); + } + + static Coord euler(float r, float phi) { + return Coord(r*cos(phi), r*sin(phi)); + } + + T dot(const Coord &a) const {return x*a.x+y*a.y;} + T mag_sq() const {return x*x+y*y;} + + void operator+= (const Coord a) {x+=a.x; y+=a.y;} + void operator-= (const Coord a) {x-=a.x; y-=a.y;} + void operator*= (T a) {x*=a; y*=a;} + /*json serialize() { + return {x,y}; + }*/ + std::array as_array () const {return {x, y};} + }; + + + + typedef Coord Coordf; + typedef Coord Coordi; + + class Color { + public : + float r; + float g; + float b; + Color(double ir, double ig, double ib): r(ir), g(ig), b(ib) {} + //Color(unsigned int ir, unsigned ig, unsigned ib): r(ir/255.), g(ig/255.), b(ib/255.) {} + static Color new_from_int(unsigned int ir, unsigned ig, unsigned ib) { + return Color(ir/255.0, ig/255.0, ib/255.0); + + } + Color(): r(0), g(0), b(0) {} + }; + + constexpr int64_t operator "" _mm(long double i) { + return i*1e6; + } + constexpr int64_t operator "" _mm(unsigned long long int i) { + return i*1000000; + } + +} + diff --git a/common/hole.cpp b/common/hole.cpp new file mode 100644 index 000000000..dca87e0c8 --- /dev/null +++ b/common/hole.cpp @@ -0,0 +1,28 @@ +#include "hole.hpp" +#include "json.hpp" + +namespace horizon { + using json = nlohmann::json; + + Hole::Hole(const UUID &uu, const json &j): + uuid(uu), + position(j.at("position").get>()), + diameter(j.at("diameter").get()), + plated(j.at("plated").get()) + { + } + + UUID Hole::get_uuid() const { + return uuid; + } + + Hole::Hole(const UUID &uu): uuid(uu) {} + + json Hole::serialize() const { + json j; + j["position"] = position.as_array(); + j["diameter"] = diameter; + j["plated"] = plated; + return j; + } +} diff --git a/common/hole.hpp b/common/hole.hpp new file mode 100644 index 000000000..c7ba266ec --- /dev/null +++ b/common/hole.hpp @@ -0,0 +1,30 @@ +#pragma once +#include "uuid.hpp" +#include "common.hpp" +#include "uuid_provider.hpp" +#include "json_fwd.hpp" +#include +#include +#include + + +namespace horizon { + using json = nlohmann::json; + + + class Hole: public UUIDProvider { + public : + Hole(const UUID &uu, const json &j); + Hole(const UUID &uu); + + UUID uuid; + Coord position; + uint64_t diameter=0.5_mm; + bool plated = false; + + virtual UUID get_uuid() const ; + + //not stored + json serialize() const; + }; +} diff --git a/common/junction.cpp b/common/junction.cpp new file mode 100644 index 000000000..fe3019da3 --- /dev/null +++ b/common/junction.cpp @@ -0,0 +1,29 @@ +#include "junction.hpp" +#include "lut.hpp" +#include "json.hpp" + +namespace horizon { + using json = nlohmann::json; + + Junction::Junction(const UUID &uu, const json &j): + uuid(uu), + position(j.at("position").get>()), + temp(false) + { + } + + Coordi Junction::get_position() const { + return position; + } + UUID Junction::get_uuid() const { + return uuid; + } + + Junction::Junction(const UUID &uu): uuid(uu) {} + + json Junction::serialize() const { + json j; + j["position"] = position.as_array(); + return j; + } +} diff --git a/common/junction.hpp b/common/junction.hpp new file mode 100644 index 000000000..b81d6186e --- /dev/null +++ b/common/junction.hpp @@ -0,0 +1,44 @@ +#pragma once +#include "uuid.hpp" +#include "common.hpp" +#include "object.hpp" +#include "position_provider.hpp" +#include "uuid_provider.hpp" +#include "net.hpp" +#include "uuid_ptr.hpp" +#include "json_fwd.hpp" +#include "bus.hpp" +#include +#include +#include + + +namespace horizon { + using json = nlohmann::json; + + + class Junction: public PositionProvider { + public : + Junction(const UUID &uu, const json &j); + Junction(const UUID &uu); + + UUID uuid; + Coord position; + uuid_ptr net=nullptr; + uuid_ptr bus = nullptr; + UUID net_segment = UUID(); + + virtual Coordi get_position() const ; + virtual UUID get_uuid() const ; + + //not stored + bool temp; + bool warning = false; + int layer = 10000; + bool needs_via = false; + bool has_via = false; + unsigned int connection_count = 0; + + json serialize() const; + }; +} diff --git a/common/line.cpp b/common/line.cpp new file mode 100644 index 000000000..abf7c93b7 --- /dev/null +++ b/common/line.cpp @@ -0,0 +1,27 @@ +#include "line.hpp" +#include "lut.hpp" +#include "json.hpp" +#include "object.hpp" + +namespace horizon { + + Line::Line(const UUID &uu, const json &j, Object &obj): + uuid(uu), + to(obj.get_junction(j.at("to").get())), + from(obj.get_junction(j.at("from").get())), + width(j.value("width", 0)), + layer(j.value("layer", 0)) + { + } + + Line::Line(UUID uu): uuid(uu) {} + + json Line::serialize() const { + json j; + j["from"] = (std::string)from.uuid; + j["to"] = (std::string)to.uuid; + j["width"] = width; + j["layer"] = layer; + return j; + } +} diff --git a/common/line.hpp b/common/line.hpp new file mode 100644 index 000000000..4b18a3dd0 --- /dev/null +++ b/common/line.hpp @@ -0,0 +1,30 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "common.hpp" +#include "junction.hpp" +#include "object.hpp" +#include "position_provider.hpp" +#include "uuid_ptr.hpp" +#include +#include +#include + + +namespace horizon { + using json = nlohmann::json; + + + class Line { + public : + Line(const UUID &uu, const json &j, Object &obj); + Line(UUID uu); + + UUID uuid; + uuid_ptr to; + uuid_ptr from; + uint64_t width = 0; + int layer = 0; + json serialize() const; + }; +} diff --git a/common/lut.hpp b/common/lut.hpp new file mode 100644 index 000000000..ab134f7cc --- /dev/null +++ b/common/lut.hpp @@ -0,0 +1,28 @@ +#pragma once +namespace horizon { + template class LutEnumStr { + static_assert(std::is_enum::value, "Must be an enum type"); + public : + LutEnumStr(std::initializer_list> s) { + for(auto it: s) { + fwd.insert(it); + rev.insert(std::make_pair(it.second, it.first)); + } + } + + const T lookup(const std::string s) const { + return fwd.at(s); + } + const std::string lookup_reverse(const T s) const { + return rev.at(s); + } + + + + private : + std::map fwd; + std::map rev; + + }; + +} diff --git a/common/polygon.cpp b/common/polygon.cpp new file mode 100644 index 000000000..b38949d2a --- /dev/null +++ b/common/polygon.cpp @@ -0,0 +1,137 @@ +#include "polygon.hpp" +#include "json.hpp" +#include "lut.hpp" + +namespace horizon { + + static const LutEnumStr type_lut = { + {"line", Polygon::Vertex::Type::LINE}, + {"arc" , Polygon::Vertex::Type::ARC} + }; + + Polygon::Vertex::Vertex(const json &j) : + type(type_lut.lookup(j.at("type").get())), + position(j.at("position").get>()), + arc_center(j.at("arc_center").get>()), + arc_reverse(j.value("arc_reverse", false)) + { + } + + Polygon::Vertex::Vertex(const Coordi &c): position(c) {} + + json Polygon::Vertex::serialize() const { + json j; + j["type"] = type_lut.lookup_reverse(type); + j["position"] = position.as_array(); + j["arc_center"] = arc_center.as_array(); + j["arc_reverse"] = arc_reverse; + return j; + } + + Polygon::Polygon(const UUID &uu, const json &j): + uuid(uu), + layer(j.value("layer", 0)) + { + { + const json &o = j["vertices"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + vertices.emplace_back(it.value()); + } + } + } + + Polygon::Polygon(const UUID &uu): uuid(uu) {} + + static int64_t sq(int64_t x) { + return x*x; + } + + Polygon Polygon::remove_arcs(unsigned int precision) const { + Polygon out(uuid); + out.layer = layer; + for(auto it=vertices.cbegin(); ittype == Polygon::Vertex::Type::LINE) { + out.vertices.emplace_back(*it); + } + else { + out.append_vertex(it->position); + auto it_next = it+1; + if(it_next == vertices.cend()) { + it_next = vertices.cbegin(); + } + Coord a(it->position); + Coord b(it_next->position); + Coord c(it->arc_center); + if((sq(c.x-a.x) + sq(c.y-a.y)) != (sq(c.x-b.x) + sq(c.y-b.y))) { + continue; + } + double radius = sqrt(sq(c.x-a.x) + sq(c.y-a.y)); + Color co(1,1,0); + double a0 = atan2(a.y-c.y, a.x-c.x); + double a1 = atan2(b.y-c.y, b.x-c.x); + + unsigned int segments = precision; + if(a0 < 0) { + a0 += 2*M_PI; + } + if(a1 < 0) { + a1 += 2*M_PI; + } + double dphi = a1-a0; + if(dphi < 0) { + dphi += 2*M_PI; + } + if(it->arc_reverse) { + dphi -= 2*M_PI; + } + dphi /= segments; + segments--; + while(segments--) { + a0 += dphi; + auto p1f = c+Coord::euler(radius, a0); + Coordi p1(p1f.x, p1f.y); + out.append_vertex(p1); + + } + + } + } + + return out; + } + + bool Polygon::has_arcs() const { + for(const auto &it: vertices) { + if(it.type == Polygon::Vertex::Type::ARC) + return true; + } + return false; + } + + bool Polygon::is_valid() const { + if(has_arcs()) + return true; + return vertices.size()>=3; + } + + Polygon::Vertex *Polygon::append_vertex(const Coordi &pos) { + vertices.emplace_back(); + vertices.back().position = pos; + return &vertices.back(); + } + + std::pair Polygon::get_vertices_for_edge(unsigned int edge) { + return {edge, (edge+1)%vertices.size()}; + } + + json Polygon::serialize() const { + json j; + j["layer"] = layer; + j["vertices"] = json::array(); + for(const auto &it: vertices) { + j["vertices"].push_back(it.serialize()); + } + + return j; + } +} diff --git a/common/polygon.hpp b/common/polygon.hpp new file mode 100644 index 000000000..6dc09df79 --- /dev/null +++ b/common/polygon.hpp @@ -0,0 +1,51 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "common.hpp" +#include "junction.hpp" +#include "object.hpp" +#include "position_provider.hpp" +#include "uuid_ptr.hpp" +#include +#include +#include + + +namespace horizon { + using json = nlohmann::json; + + + class Polygon { + public : + class Vertex { + public: + enum class Type {LINE, ARC}; + + Vertex(const json &j); + Vertex(const Coordi &c); + Vertex() {} + json serialize() const; + bool remove = false; + + Type type = Type::LINE; + Coordi position; + Coordi arc_center; + bool arc_reverse = false; + }; + + + Polygon(const UUID &uu, const json &j); + Polygon(const UUID &uu); + Vertex *append_vertex(const Coordi &pos=Coordi()); + std::pair get_vertices_for_edge(unsigned int edge); + Polygon remove_arcs(unsigned int precision=16) const; + bool has_arcs() const; + bool is_valid() const; + + UUID uuid; + std::deque vertices; + int layer = 0; + bool temp = false; + json serialize() const; + }; +} diff --git a/common/text.cpp b/common/text.cpp new file mode 100644 index 000000000..8bbdd3ed5 --- /dev/null +++ b/common/text.cpp @@ -0,0 +1,51 @@ +#include "text.hpp" +#include "lut.hpp" +#include "json.hpp" + +namespace horizon { + + static const LutEnumStr orientation_lut = { + {"up", Orientation::UP}, + {"down", Orientation::DOWN}, + {"left", Orientation::LEFT}, + {"right", Orientation::RIGHT}, + }; + static const LutEnumStr placement_lut = { + {"baseline", TextPlacement::BASELINE}, + {"center", TextPlacement::CENTER}, + {"bottom", TextPlacement::BOTTOM}, + }; + + Text::Text(const UUID &uu, const json &j): + uuid(uu), + position(j.at("position").get>()), + placement(placement_lut.lookup(j["placement"])), + orientation(orientation_lut.lookup(j["orientation"])), + text(j.at("text").get()), + size(j.value("size", 2500000)), + width(j.value("width", 0)), + layer(j.value("layer", 0)), + from_smash(j.value("from_smash", false)), + temp(false) + { + } + + Text::Text(const UUID &uu): uuid(uu) {} + + json Text::serialize() const { + json j; + j["position"] = position.as_array(); + j["placement"] = placement_lut.lookup_reverse(placement); + j["orientation"] = orientation_lut.lookup_reverse(orientation); + j["text"] = text; + j["size"] = size; + j["width"] = width; + j["layer"] = layer; + j["from_smash"] = from_smash; + return j; + } + + UUID Text::get_uuid() const { + return uuid; + } +} diff --git a/common/text.hpp b/common/text.hpp new file mode 100644 index 000000000..73d7273f9 --- /dev/null +++ b/common/text.hpp @@ -0,0 +1,40 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "common.hpp" +#include "object.hpp" +#include "uuid_provider.hpp" +#include +#include +#include + + +namespace horizon { + using json = nlohmann::json; + enum class TextPlacement {BASELINE, CENTER, BOTTOM}; + + class Text: public UUIDProvider { + public : + Text(const UUID &uu, const json &j); + Text(const UUID &uu); + + UUID uuid; + Coordi position; + + TextPlacement placement = TextPlacement::CENTER; + Orientation orientation = Orientation::RIGHT; + std::string text; + uint64_t size = 1250000; + uint64_t width = 0; + int layer = 0; + std::string text_override; + bool overridden = false; + bool from_smash = false; + + //not stored + bool temp; + + UUID get_uuid() const override; + json serialize() const; + }; +} diff --git a/constraints/clearance.cpp b/constraints/clearance.cpp new file mode 100644 index 000000000..90ffc1029 --- /dev/null +++ b/constraints/clearance.cpp @@ -0,0 +1,23 @@ +#include "clearance.hpp" +#include "json.hpp" +#include "constraints.hpp" +#include "common.hpp" + +namespace horizon { + Clearance::Clearance(const UUIDPath<2> &uu, const json &j, Constraints &co): + netclass_a(&co.net_classes.at(uu.at(0))), + netclass_b(&co.net_classes.at(uu.at(1))), + routing_clearance(j.at("routing_clearance")) + { + + } + + Clearance::Clearance(const json &j): routing_clearance(j.at("routing_clearance")) {} + Clearance::Clearance(): routing_clearance(1_mm){} + + json Clearance::serialize() const { + json j; + j["routing_clearance"] = routing_clearance; + return j; + } +} diff --git a/constraints/clearance.hpp b/constraints/clearance.hpp new file mode 100644 index 000000000..3631dc263 --- /dev/null +++ b/constraints/clearance.hpp @@ -0,0 +1,23 @@ +#pragma once +#include "uuid.hpp" +#include "uuid_path.hpp" +#include "json_fwd.hpp" +#include "net_class.hpp" +#include + +namespace horizon { + using json = nlohmann::json; + + class Clearance { + public : + Clearance(const UUIDPath<2> &uu, const json &j, class Constraints &co); + Clearance(const json &j); + Clearance(); + NetClass *netclass_a = nullptr; + NetClass *netclass_b = nullptr; + uint64_t routing_clearance; + + json serialize() const; + }; + +} diff --git a/constraints/constraints.cpp b/constraints/constraints.cpp new file mode 100644 index 000000000..d9bfd6b30 --- /dev/null +++ b/constraints/constraints.cpp @@ -0,0 +1,71 @@ +#include "constraints.hpp" +#include "json.hpp" +#include + +namespace horizon { + Constraints::Constraints(const json &j): + default_clearance(j.at("default_clearance")) + { + if(j.count("net_classes")) { + const json &o = j["net_classes"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + net_classes.emplace(std::make_pair(u, NetClass(u, it.value()))); + } + } + if(j.count("clearances")) { + const json &o = j["clearances"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUIDPath<2>(it.key()); + clearances.emplace(std::make_pair(u, Clearance(u, it.value(), *this))); + } + } + default_net_class = &net_classes.at(j.at("default_net_class").get()); + } + + Constraints::Constraints() { + auto uu = UUID::random(); + default_net_class = &net_classes.emplace(uu,uu).first->second; + } + + Constraints Constraints::new_from_file(const std::string &filename) { + json j; + std::ifstream ifs(filename); + if(!ifs.is_open()) { + throw std::runtime_error("file " +filename+ " not opened"); + } + ifs>>j; + ifs.close(); + return Constraints(j); + } + + Clearance *Constraints::get_clearance(const NetClass *nc_a, const NetClass *nc_b) { + UUIDPath<2> uup(nc_a->uuid, nc_b->uuid); + if(clearances.count(uup)) { + return &clearances.at(uup); + } + uup = UUIDPath<2>(nc_b->uuid, nc_a->uuid); + if(clearances.count(uup)) { + return &clearances.at(uup); + } + return &default_clearance; + } + + json Constraints::serialize() const { + json j; + j["type"] = "constraints"; + + j["net_classes"] = json::object(); + for(const auto &it: net_classes) { + j["net_classes"][(std::string)it.first] = it.second.serialize(); + } + j["clearances"] = json::object(); + for(const auto &it: clearances) { + j["clearances"][(std::string)it.first] = it.second.serialize(); + } + j["default_clearance"] = default_clearance.serialize(); + j["default_net_class"] = (std::string)default_net_class->uuid; + + return j; + } +} diff --git a/constraints/constraints.hpp b/constraints/constraints.hpp new file mode 100644 index 000000000..5ad2932f9 --- /dev/null +++ b/constraints/constraints.hpp @@ -0,0 +1,28 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "net_class.hpp" +#include "clearance.hpp" +#include + +namespace horizon { + using json = nlohmann::json; + + class Constraints { + private : + Constraints(const json &j); + + public : + static Constraints new_from_file(const std::string &filename); + Constraints(); + + std::map net_classes; + std::map, Clearance> clearances; + Clearance *get_clearance(const NetClass *nc_a, const NetClass *nc_b); + Clearance default_clearance; + NetClass *default_net_class = nullptr; + + json serialize() const; + }; + +} diff --git a/constraints/net_class.cpp b/constraints/net_class.cpp new file mode 100644 index 000000000..cf150bbae --- /dev/null +++ b/constraints/net_class.cpp @@ -0,0 +1,24 @@ +#include "net_class.hpp" +#include "json.hpp" +#include "common.hpp" + +namespace horizon { + NetClass::NetClass(const UUID &uu, const json &j): + uuid(uu), + name(j.at("name").get()), + min_width(j.at("min_width")), + default_width(j.at("default_width")) + { + + } + + NetClass::NetClass(const UUID &uu): uuid(uu), name("default"), min_width(0.2_mm), default_width(0.5_mm) {} + + json NetClass::serialize() const { + json j; + j["name"] = name; + j["min_width"] = min_width; + j["default_width"] = default_width; + return j; + } +} diff --git a/constraints/net_class.hpp b/constraints/net_class.hpp new file mode 100644 index 000000000..547e0c8be --- /dev/null +++ b/constraints/net_class.hpp @@ -0,0 +1,21 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include + +namespace horizon { + using json = nlohmann::json; + + class NetClass { + public : + NetClass(const UUID &uu, const json &j); + NetClass(const UUID &uu); + UUID uuid; + std::string name; + uint64_t min_width; + uint64_t default_width; + + json serialize() const; + }; + +} diff --git a/core/buffer.cpp b/core/buffer.cpp new file mode 100644 index 000000000..1766da459 --- /dev/null +++ b/core/buffer.cpp @@ -0,0 +1,191 @@ +#include "buffer.hpp" +#include "json.hpp" +#include "core_package.hpp" +#include "core_schematic.hpp" +#include "core_symbol.hpp" + +namespace horizon { + Buffer::Buffer(Core *co):core(co) {} + + void Buffer::clear() { + texts.clear(); + lines.clear(); + junctions.clear(); + arcs.clear(); + pads.clear(); + holes.clear(); + polygons.clear(); + } + + void Buffer::load_from_symbol(std::set selection) { + clear(); + std::set new_sel; + for(const auto &it : selection) { + switch(it.type) { + case ObjectType::LINE : { + Line *line = core.r->get_line(it.uuid); + new_sel.emplace(line->from.uuid, ObjectType::JUNCTION); + new_sel.emplace(line->to.uuid, ObjectType::JUNCTION); + } break; + case ObjectType::POLYGON_EDGE : { + Polygon *poly = core.r->get_polygon(it.uuid); + new_sel.emplace(poly->uuid, ObjectType::POLYGON); + } break; + case ObjectType::POLYGON_VERTEX : { + Polygon *poly = core.r->get_polygon(it.uuid); + new_sel.emplace(poly->uuid, ObjectType::POLYGON); + } break; + case ObjectType::ARC : { + Arc *arc = core.r->get_arc(it.uuid); + new_sel.emplace(arc->from.uuid, ObjectType::JUNCTION); + new_sel.emplace(arc->to.uuid, ObjectType::JUNCTION); + new_sel.emplace(arc->center.uuid, ObjectType::JUNCTION); + } break; + case ObjectType::SCHEMATIC_SYMBOL : { + auto &sym = core.c->get_sheet()->symbols.at(it.uuid); + new_sel.emplace(sym.component->uuid , ObjectType::COMPONENT); + } break; + + /* + case ObjectType::NET_LABEL : { + auto &la = core.c->get_sheet()->net_labels.at(it.uuid); + new_sel.emplace(la.junction->uuid, ObjectType::JUNCTION); + } break; + case ObjectType::BUS_LABEL : { + auto &la = core.c->get_sheet()->bus_labels.at(it.uuid); + new_sel.emplace(la.junction->uuid, ObjectType::JUNCTION); + } break; + case ObjectType::POWER_SYMBOL : { + auto &ps = core.c->get_sheet()->power_symbols.at(it.uuid); + new_sel.emplace(ps.junction->uuid, ObjectType::JUNCTION); + } break; + case ObjectType::BUS_RIPPER : { + auto &rip = core.c->get_sheet()->bus_rippers.at(it.uuid); + new_sel.emplace(rip.junction->uuid, ObjectType::JUNCTION); + } break; + + case ObjectType::LINE_NET : { + auto line = &core.c->get_sheet()->net_lines.at(it.uuid); + for(auto &it_ft: {line->from, line->to}) { + if(it_ft.is_junc()) { + new_sel.emplace(it_ft.junc.uuid, ObjectType::JUNCTION); + } + } + } break; + */ + + + default:; + } + } + selection.insert(new_sel.begin(), new_sel.end()); + new_sel.clear(); + for(const auto &it : selection) { + if(it.type == ObjectType::COMPONENT) { + auto &comp = core.c->get_schematic()->block->components.at(it.uuid); + for(const auto &it_conn: comp.connections) { + new_sel.emplace(it_conn.second.net->uuid, ObjectType::NET); + } + + } + } + selection.insert(new_sel.begin(), new_sel.end()); + + + + for(const auto &it: selection) { + if(it.type == ObjectType::TEXT) { + auto x = core.r->get_text(it.uuid); + texts.emplace(x->uuid, *x); + } + else if(it.type == ObjectType::JUNCTION) { + auto x = core.r->get_junction(it.uuid); + junctions.emplace(x->uuid, *x); + } + else if(it.type == ObjectType::PAD) { + auto x = &core.k->get_package()->pads.at(it.uuid); + pads.emplace(x->uuid, *x); + } + else if(it.type == ObjectType::NET) { + auto &x = core.c->get_schematic()->block->nets.at(it.uuid); + nets.emplace(x.uuid, x); + } + else if(it.type == ObjectType::SYMBOL_PIN) { + auto x = core.y->get_symbol_pin(it.uuid); + pins.emplace(x->uuid, *x); + } + else if(it.type == ObjectType::HOLE) { + auto x = core.r->get_hole(it.uuid); + holes.emplace(x->uuid, *x); + } + else if(it.type == ObjectType::POLYGON) { + auto x = core.r->get_polygon(it.uuid); + polygons.emplace(x->uuid, *x); + } + + } + for(const auto &it: selection) { + if(it.type == ObjectType::LINE) { + auto x = core.r->get_line(it.uuid); + auto &li = lines.emplace(x->uuid, *x).first->second; + li.from = &junctions.at(li.from.uuid); + li.to = &junctions.at(li.to.uuid); + } + else if(it.type == ObjectType::ARC) { + auto x = core.r->get_arc(it.uuid); + auto &arc = arcs.emplace(x->uuid, *x).first->second; + arc.from = &junctions.at(arc.from.uuid); + arc.to = &junctions.at(arc.to.uuid); + arc.center = &junctions.at(arc.center.uuid); + } + else if(it.type == ObjectType::COMPONENT) { + auto &x = core.c->get_schematic()->block->components.at(it.uuid); + auto &comp = components.emplace(x.uuid, x).first->second; + for(auto &it_conn: comp.connections) { + it_conn.second.net.update(nets); + } + } + } + for(const auto &it: selection) { + if(it.type == ObjectType::SCHEMATIC_SYMBOL) { + auto &x = core.c->get_sheet()->symbols.at(it.uuid); + auto &sym = symbols.emplace(x.uuid, x).first->second; + sym.component.update(components); + sym.gate.update(sym.component->entity->gates); + } + } + } + + json Buffer::serialize() { + json j; + j["texts"] = json::object(); + for(const auto &it: texts) { + j["texts"][(std::string)it.first] = it.second.serialize(); + } + j["junctions"] = json::object(); + for(const auto &it: junctions) { + j["junctions"][(std::string)it.first] = it.second.serialize(); + } + j["lines"] = json::object(); + for(const auto &it: lines) { + j["lines"][(std::string)it.first] = it.second.serialize(); + } + j["arcs"] = json::object(); + for(const auto &it: arcs) { + j["arcs"][(std::string)it.first] = it.second.serialize(); + } + j["pads"] = json::object(); + for(const auto &it: pads) { + j["pads"][(std::string)it.first] = it.second.serialize(); + } + j["holes"] = json::object(); + for(const auto &it: holes) { + j["holes"][(std::string)it.first] = it.second.serialize(); + } + j["polygons"] = json::object(); + for(const auto &it: polygons) { + j["polygons"][(std::string)it.first] = it.second.serialize(); + } + return j; + } +} diff --git a/core/buffer.hpp b/core/buffer.hpp new file mode 100644 index 000000000..b8989e59a --- /dev/null +++ b/core/buffer.hpp @@ -0,0 +1,44 @@ +#pragma once +#include +#include +#include "uuid.hpp" +#include "text.hpp" +#include "junction.hpp" +#include "line.hpp" +#include "arc.hpp" +#include "pad.hpp" +#include "cores.hpp" +#include "core.hpp" +#include "component.hpp" +#include "schematic_symbol.hpp" +#include "symbol.hpp" +#include "hole.hpp" +#include "polygon.hpp" +#include "net.hpp" +#include "json_fwd.hpp" + +namespace horizon { + class Buffer { + public: + Buffer(Core *co); + void clear(); + void load_from_symbol(std::set selection); + + std::map texts; + std::map junctions; + std::map lines; + std::map arcs; + std::map pads; + std::map polygons; + std::map components; + std::map symbols; + std::map pins; + std::map nets; + std::map holes; + + json serialize(); + + private: + Cores core; + }; +} diff --git a/core/clipboard.cpp b/core/clipboard.cpp new file mode 100644 index 000000000..248eb4ae5 --- /dev/null +++ b/core/clipboard.cpp @@ -0,0 +1,65 @@ +#include "clipboard.hpp" +#include "canvas/canvas_cairo.hpp" +#include "json.hpp" + + +namespace horizon { + ClipboardManager::ClipboardManager(Core *co):buffer(co), core(co) {} + + void ClipboardManager::copy(std::set selection, const Coordi &cp) { + std::cout<<"copy" < refClipboard = Gtk::Clipboard::get(); + + //Targets: + std::vector targets; + targets.push_back( Gtk::TargetEntry("imp-buffer") ); + //targets.push_back( Gtk::TargetEntry("image/png") ); + targets.push_back( Gtk::TargetEntry("image/svg+xml") ); + + refClipboard->set( targets, + sigc::mem_fun(this, &ClipboardManager::on_clipboard_get), + sigc::mem_fun(this, &ClipboardManager::on_clipboard_clear) ); + } + + void ClipboardManager::on_clipboard_get(Gtk::SelectionData& selection_data, guint /* info */) { + const std::string target = selection_data.get_target(); + std::cout << "get target " <load_icon("weather-clear", 32); + selection_data.set_pixbuf(pb); + } + else if(target == "image/svg+xml") { + + std::stringstream stream; + { + Cairo::RefPtr surface = Cairo::SvgSurface::create_for_stream([&stream](const unsigned char* c, unsigned int n)->Cairo::ErrorStatus{ + stream.write(reinterpret_cast(c),n); + return CAIRO_STATUS_SUCCESS; + } + , 10e3, 10e3); + + Cairo::RefPtr cr = Cairo::Context::create(surface); + cr->translate(1e3, 0); + CanvasCairo ca(cr); + ca.set_core(core); + ca.update(buffer); + } + + selection_data.set("image/svg+xml", stream.str()); + } + + } + void ClipboardManager::on_clipboard_clear() { + //buffer.clear(); + std::cout<<"clipboard clear"< +#include "canvas/selectables.hpp" +#include "core.hpp" +#include "buffer.hpp" +#include + +namespace horizon { + class ClipboardManager { + public: + ClipboardManager(Core *co); + void copy(std::set selection, const Coordi &cursor_pos); + + private: + void on_clipboard_get(Gtk::SelectionData& selection_data, guint /* info */); + void on_clipboard_clear(); + Buffer buffer; + Core *core; + Coordi cursor_pos; + }; +} diff --git a/core/core.cpp b/core/core.cpp new file mode 100644 index 000000000..f28b12315 --- /dev/null +++ b/core/core.cpp @@ -0,0 +1,384 @@ +#include "core.hpp" +#include "tool_move.hpp" +#include "tool_place_junction.hpp" +#include "tool_draw_line.hpp" +#include "tool_delete.hpp" +#include "tool_draw_arc.hpp" +#include "tool_map_pin.hpp" +#include "tool_map_symbol.hpp" +#include "tool_draw_line_net.hpp" +#include "tool_add_component.hpp" +#include "tool_place_text.hpp" +#include "tool_place_net_label.hpp" +#include "tool_disconnect.hpp" +#include "tool_bend_line_net.hpp" +#include "tool_move_net_segment.hpp" +#include "tool_place_power_symbol.hpp" +#include "tool_edit_component_pin_names.hpp" +#include "tool_place_bus_label.hpp" +#include "tool_place_bus_ripper.hpp" +#include "tool_manage_buses.hpp" +#include "tool_draw_polygon.hpp" +#include "tool_enter_datum.hpp" +#include "tool_place_hole.hpp" +#include "tool_place_pad.hpp" +#include "tool_paste.hpp" +#include "tool_assign_part.hpp" +#include "tool_map_package.hpp" +#include "tool_draw_track.hpp" +#include "tool_place_via.hpp" +#include "tool_route_track.hpp" +#include "tool_drag_keep_slope.hpp" +#include "tool_add_part.hpp" +#include "tool_smash.hpp" +#include "json.hpp" +#include + +namespace horizon { + + std::unique_ptr Core::create_tool(ToolID tool_id) { + switch(tool_id) { + case ToolID::MOVE : + case ToolID::MOVE_EXACTLY : + case ToolID::MIRROR : + case ToolID::ROTATE : + return std::make_unique(this, tool_id); + + case ToolID::PLACE_JUNCTION : + return std::make_unique(this, tool_id); + + case ToolID::DRAW_LINE : + return std::make_unique(this, tool_id); + + case ToolID::DELETE : + return std::make_unique(this, tool_id); + + case ToolID::DRAW_ARC : + return std::make_unique(this, tool_id); + + case ToolID::MAP_PIN : + return std::make_unique(this, tool_id); + + case ToolID::MAP_SYMBOL : + return std::make_unique(this, tool_id); + + case ToolID::DRAW_NET : + return std::make_unique(this, tool_id); + + case ToolID::ADD_COMPONENT: + return std::make_unique(this, tool_id); + + case ToolID::PLACE_TEXT: + return std::make_unique(this, tool_id); + + case ToolID::PLACE_NET_LABEL: + return std::make_unique(this, tool_id); + + case ToolID::DISCONNECT: + return std::make_unique(this, tool_id); + + case ToolID::BEND_LINE_NET: + return std::make_unique(this, tool_id); + + case ToolID::SELECT_NET_SEGMENT: + case ToolID::MOVE_NET_SEGMENT: + case ToolID::MOVE_NET_SEGMENT_NEW: + return std::make_unique(this, tool_id); + + case ToolID::PLACE_POWER_SYMBOL: + return std::make_unique(this, tool_id); + + case ToolID::EDIT_COMPONENT_PIN_NAMES: + return std::make_unique(this, tool_id); + + case ToolID::PLACE_BUS_LABEL: + return std::make_unique(this, tool_id); + + case ToolID::PLACE_BUS_RIPPER: + return std::make_unique(this, tool_id); + + case ToolID::MANAGE_BUSES: + return std::make_unique(this, tool_id); + + case ToolID::ANNOTATE: + return std::make_unique(this, tool_id); + + case ToolID::DRAW_POLYGON: + return std::make_unique(this, tool_id); + + case ToolID::ENTER_DATUM: + return std::make_unique(this, tool_id); + + case ToolID::PLACE_HOLE: + return std::make_unique(this, tool_id); + + case ToolID::PLACE_PAD: + return std::make_unique(this, tool_id); + + case ToolID::PASTE: + return std::make_unique(this, tool_id); + + case ToolID::ASSIGN_PART: + return std::make_unique(this, tool_id); + + case ToolID::MAP_PACKAGE: + return std::make_unique(this, tool_id); + + case ToolID::DRAW_TRACK: + return std::make_unique(this, tool_id); + + case ToolID::PLACE_VIA: + return std::make_unique(this, tool_id); + + case ToolID::ROUTE_TRACK: + return std::make_unique(this, tool_id); + + case ToolID::DRAG_KEEP_SLOPE: + return std::make_unique(this, tool_id); + + case ToolID::ADD_PART: + return std::make_unique(this, tool_id); + + case ToolID::SMASH: + return std::make_unique(this, tool_id); + + case ToolID::UNSMASH: + return std::make_unique(this, tool_id); + + + default: + return nullptr; + } + } + + ToolResponse Core::tool_begin(ToolID tool_id, const ToolArgs &args) { + if(!args.keep_selection) { + selection.clear(); + selection = args.selection; + } + + tool = create_tool(tool_id); + if(tool) { + s_signal_tool_changed.emit(tool_id); + auto r = tool->begin(args); + if(r.end_tool) { + s_signal_tool_changed.emit(ToolID::NONE); + tool.reset(); + rebuild(); + } + + return r; + } + + return ToolResponse(); + } + + + bool Core::tool_can_begin(ToolID tool_id, const std::set &sel) { + auto t = create_tool(tool_id); + auto sel_saved = selection; + selection = sel; + auto r = t->can_begin(); + selection = sel_saved; + return r; + } + + static const std::map layers = { + {0, {0, "Default", {1,1,0}}}, + }; + + const std::map &Core::get_layers() { + return layers; + } + + static std::vector layers_sorted; + + const std::vector &Core::get_layers_sorted() { + if(layers_sorted.size() == 0) { + layers_sorted.reserve(layers.size()); + for(const auto &it: get_layers()) { + layers_sorted.push_back(it.first); + } + std::sort(layers_sorted.begin(), layers_sorted.end()); + } + return layers_sorted; + } + + ToolResponse Core::tool_update(const ToolArgs &args) { + if(tool) { + auto r = tool->update(args); + if(r.end_tool) { + s_signal_tool_changed.emit(ToolID::NONE); + tool.reset(); + rebuild(); + } + return r; + } + return ToolResponse(); + } + + const std::string Core::get_tool_name() { + if(tool) { + return tool->name; + } + return "None"; + } + + + + + Junction *Core::insert_junction(const UUID &uu, bool work) { + auto map = get_junction_map(work); + auto x = map->emplace(std::make_pair(uu, uu)); + return &(x.first->second); + } + + Junction *Core::get_junction(const UUID &uu, bool work) { + auto map = get_junction_map(work); + return &map->at(uu); + } + + void Core::delete_junction(const UUID &uu, bool work) { + auto map = get_junction_map(work); + map->erase(uu); + } + + Line *Core::insert_line(const UUID &uu, bool work) { + auto map = get_line_map(work); + auto x = map->emplace(std::make_pair(uu, uu)); + return &(x.first->second); + } + + Line *Core::get_line(const UUID &uu, bool work) { + auto map = get_line_map(work); + return &map->at(uu); + } + + void Core::delete_line(const UUID &uu, bool work) { + auto map = get_line_map(work); + map->erase(uu); + } + + std::vector Core::get_lines(bool work) { + auto *map = get_line_map(work); + std::vector r; + if(!map) + return r; + for(auto &it: *map) { + r.push_back(&it.second); + } + return r; + } + + Arc *Core::insert_arc(const UUID &uu, bool work) { + auto map = get_arc_map(work); + auto x = map->emplace(std::make_pair(uu, uu)); + return &(x.first->second); + } + + Arc *Core::get_arc(const UUID &uu, bool work) { + auto map = get_arc_map(work); + return &map->at(uu); + } + + void Core::delete_arc(const UUID &uu, bool work) { + auto map = get_arc_map(work); + map->erase(uu); + } + + std::vector Core::get_arcs(bool work) { + auto *map = get_arc_map(work); + std::vector r; + if(!map) + return r; + for(auto &it: *map) { + r.push_back(&it.second); + } + return r; + } + + Text *Core::insert_text(const UUID &uu, bool work) { + auto map = get_text_map(work); + auto x = map->emplace(uu, uu); + return &(x.first->second); + } + + Text *Core::get_text(const UUID &uu, bool work) { + auto map = get_text_map(work); + return &map->at(uu); + } + + void Core::delete_text(const UUID &uu, bool work) { + auto map = get_text_map(work); + map->erase(uu); + } + + Polygon *Core::insert_polygon(const UUID &uu, bool work) { + auto map = get_polygon_map(work); + auto x = map->emplace(std::make_pair(uu, uu)); + return &(x.first->second); + } + + Polygon *Core::get_polygon(const UUID &uu, bool work) { + auto map = get_polygon_map(work); + return &map->at(uu); + } + + void Core::delete_polygon(const UUID &uu, bool work) { + auto map = get_polygon_map(work); + map->erase(uu); + } + + Hole *Core::insert_hole(const UUID &uu, bool work) { + auto map = get_hole_map(work); + auto x = map->emplace(std::make_pair(uu, uu)); + return &(x.first->second); + } + + Hole *Core::get_hole(const UUID &uu, bool work) { + auto map = get_hole_map(work); + return &map->at(uu); + } + + void Core::delete_hole(const UUID &uu, bool work) { + auto map = get_hole_map(work); + map->erase(uu); + } + + + + void Core::rebuild(bool from_undo) { + if(!from_undo && !reverted) { + while(history_current+1 != (int)history.size()) { + history.pop_back(); + } + assert(history_current+1 == (int)history.size()); + history_push(); + history_current++; + } + s_signal_rebuilt.emit(); + reverted = false; + } + + void Core::undo() { + if(history_current) { + history_current--; + history_load(history_current); + } + } + + void Core::redo() { + if(history_current+1 == (int)history.size()) + return; + std::cout<< "can redo"< +#include +#include +#include "cores.hpp" +#include "dialogs/dialogs.hpp" +#include "layer.hpp" +#include "json_fwd.hpp" +#include "constraints.hpp" +#include + +namespace horizon { + enum class ToolEventType {MOVE, CLICK, KEY, DATA}; + enum class ToolID {NONE, MOVE, PLACE_JUNCTION, DRAW_LINE, + DELETE, DRAW_ARC, ROTATE, MIRROR, MAP_PIN, MAP_SYMBOL, + DRAW_NET, ADD_COMPONENT, PLACE_TEXT, PLACE_NET_LABEL, + DISCONNECT, BEND_LINE_NET, SELECT_NET_SEGMENT, SELECT_NET, + PLACE_POWER_SYMBOL, MOVE_NET_SEGMENT, MOVE_NET_SEGMENT_NEW, + EDIT_COMPONENT_PIN_NAMES, PLACE_BUS_LABEL, PLACE_BUS_RIPPER, + MANAGE_BUSES, DRAW_POLYGON, ENTER_DATUM, MOVE_EXACTLY, PLACE_HOLE, + PLACE_PAD, PASTE, ASSIGN_PART, MAP_PACKAGE, DRAW_TRACK, PLACE_VIA, + ROUTE_TRACK, DRAG_KEEP_SLOPE, ADD_PART, ANNOTATE, SMASH, UNSMASH + }; + + class ToolArgs { + public : + ToolEventType type; + Coordi coords; + std::set selection; + bool keep_selection = false; + unsigned int button; + unsigned int key; + Target target; + int work_layer; + ToolArgs() {} + }; + + class ToolResponse { + public : + ToolID next_tool = ToolID::NONE; + bool end_tool = false; + int layer = 10000; + ToolResponse() {} + static ToolResponse end() {ToolResponse r; r.end_tool=true; return r;} + static ToolResponse change_layer(int l) {ToolResponse r; r.layer=l; return r;} + static ToolResponse next(ToolID t) {ToolResponse r; r.end_tool=true; r.next_tool = t; return r;}; + }; + + class ToolBase { + public : + ToolBase(class Core *c, ToolID tid); + virtual ToolResponse begin(const ToolArgs &args) = 0; + virtual ToolResponse update(const ToolArgs &args) = 0; + virtual bool can_begin() {return false;} + virtual ~ToolBase() {} + std::string name; + + protected : + Cores core; + ToolID tool_id = ToolID::NONE; + }; + + class Core :public sigc::trackable { + public : + const std::string get_tool_name(); + virtual bool has_object_type(ObjectType ty) {return false;} + + virtual Junction *insert_junction(const UUID &uu, bool work = true); + virtual Junction *get_junction(const UUID &uu, bool work=true); + virtual void delete_junction(const UUID &uu, bool work = true); + + virtual Line *insert_line(const UUID &uu, bool work = true); + virtual Line *get_line(const UUID &uu, bool work=true); + virtual void delete_line(const UUID &uu, bool work = true); + + virtual Arc *insert_arc(const UUID &uu, bool work = true); + virtual Arc *get_arc(const UUID &uu, bool work=true); + virtual void delete_arc(const UUID &uu, bool work = true); + + virtual Text *insert_text(const UUID &uu, bool work = true); + virtual Text *get_text(const UUID &uu, bool work=true); + virtual void delete_text(const UUID &uu, bool work = true); + + virtual Polygon *insert_polygon(const UUID &uu, bool work = true); + virtual Polygon *get_polygon(const UUID &uu, bool work=true); + virtual void delete_polygon(const UUID &uu, bool work = true); + virtual Hole *insert_hole(const UUID &uu, bool work = true); + virtual Hole *get_hole(const UUID &uu, bool work=true); + virtual void delete_hole(const UUID &uu, bool work = true); + + virtual std::vector get_lines(bool work=true); + virtual std::vector get_arcs(bool work=true); + + virtual void rebuild(bool from_undo = false); + ToolResponse tool_begin(ToolID tool_id, const ToolArgs &args); + ToolResponse tool_update(const ToolArgs &args); + bool tool_can_begin(ToolID tool_id, const std::set &selection); + virtual void commit() = 0; + virtual void revert() = 0; + virtual void save() = 0; + void undo(); + void redo(); + + + inline bool tool_is_active() {return tool!=nullptr;} + + virtual bool property_is_settable(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled=nullptr) {if(handled){*handled=false;}return true;} + + virtual std::string get_property_string(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled=nullptr); + virtual void set_property_string(const UUID &uu, ObjectType type, ObjectProperty::ID property, const std::string &value, bool *handled=nullptr); + + virtual bool get_property_bool(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled=nullptr); + virtual void set_property_bool(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool value, bool *handled=nullptr); + + virtual int64_t get_property_int(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled=nullptr); + virtual void set_property_int(const UUID &uu, ObjectType type, ObjectProperty::ID property, int64_t value, bool *handled=nullptr); + + //std::string get_property_string(const UUID &uu, ObjectType type, ObjectProperty::ID property); + //void set_property_string(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool value); + //virtual bool *get_set_property_bool(const UUID &uu, ObjectType type, ObjectProperty::ID property) {return nullptr;} + + virtual const std::map &get_layers(); + const std::vector &get_layers_sorted(); + + virtual json get_meta(); + + virtual Constraints *get_constraints() {return nullptr;} + virtual std::pair get_bbox() = 0; + + virtual ~Core() {} + std::set selection; + Pool *m_pool; + Dialogs dialogs; + + typedef sigc::signal type_signal_tool_changed; + type_signal_tool_changed signal_tool_changed() {return s_signal_tool_changed;} + typedef sigc::signal type_signal_rebuilt; + type_signal_rebuilt signal_rebuilt() {return s_signal_rebuilt;} + type_signal_rebuilt signal_save() {return s_signal_save;} + + typedef sigc::signal type_signal_request_save_meta; + type_signal_request_save_meta signal_request_save_meta() {return s_signal_request_save_meta;} + + protected : + virtual std::map *get_junction_map(bool work=true) {return nullptr;} + virtual std::map *get_line_map(bool work=true) {return nullptr;} + virtual std::map *get_arc_map(bool work=true) {return nullptr;} + virtual std::map *get_text_map(bool work=true) {return nullptr;} + virtual std::map *get_polygon_map(bool work=true) {return nullptr;} + virtual std::map *get_hole_map(bool work=true) {return nullptr;} + + + bool reverted = false; + std::unique_ptr tool=nullptr; + type_signal_tool_changed s_signal_tool_changed; + type_signal_rebuilt s_signal_rebuilt; + type_signal_rebuilt s_signal_save; + type_signal_request_save_meta s_signal_request_save_meta; + + class HistoryItem { + public: + //Symbol sym; + //HistoryItem(const Symbol &s): sym(s) {} + std::string comment; + virtual ~HistoryItem() {} + }; + std::deque> history; + int history_current = -1; + virtual void history_push() = 0; + virtual void history_load(unsigned int i) = 0; + + private: + std::unique_ptr create_tool(ToolID tool_id); + }; +} diff --git a/core/core_board.cpp b/core/core_board.cpp new file mode 100644 index 000000000..3ff78adb1 --- /dev/null +++ b/core/core_board.cpp @@ -0,0 +1,427 @@ +#include "core_board.hpp" +#include +#include "json.hpp" +#include "util.hpp" +#include "part.hpp" + +namespace horizon { + CoreBoard::CoreBoard(const std::string &board_filename, const std::string &block_filename, const std::string &constraints_filename, const std::string &via_dir, Pool &pool) : + constraints(Constraints::new_from_file(constraints_filename)), + via_padstack_provider(via_dir), + block(Block::new_from_file(block_filename, pool, &constraints)), + block_work(block), + brd(Board::new_from_file(board_filename, block, pool, via_padstack_provider)), + brd_work(brd), + m_board_filename(board_filename), + m_block_filename(block_filename), + m_constraints_filename(constraints_filename), + m_via_dir(via_dir), + m_layers({ + {50, {50, "Outline", {.6,.6, 0}}}, + {40, {40, "Top Courtyard", {.5,.5,.5}}}, + {30, {30, "Top Placement", {.5,.5,.5}}}, + {20, {20, "Top Silkscreen", {.9,.9,.9}}}, + {10, {10, "Top Mask", {1,.5,.5}}}, + {0, {0, "Top Copper", {1,0,0}, false, true}}, + {-100, {-100, "Bottom Copper", {0,.5,0}, true, true}}, + {-110, {-110, "Bottom Mask", {.25,.5,.25}, true}}, + {-120, {-120, "Bottom Silkscreen", {.9,.9,.9}, true}}, + {-130, {-130, "Bottom Placement", {.5,.5,.5}}}, + {-140, {-140, "Bottom Courtyard", {.5,.5,.5}}}, + + }) + { + for(unsigned int i = 0; isecond.component == nullptr || it->second.component->part == nullptr) { + brd.packages.erase(it++); + } + else { + it++; + } + } + brd.update_refs(); + for(auto it=brd.tracks.begin(); it!=brd.tracks.end();) { + bool del = false; + for(auto &it_ft: {it->second.from, it->second.to}) { + if(it_ft.package.uuid) { + if(brd.packages.count(it_ft.package.uuid) == 0) { + del = true; + } + else { + if(it_ft.package->component->part->pad_map.count(it_ft.pad.uuid) == 0) { + del = true; + } + } + } + } + + if(del) { + brd.tracks.erase(it++); + } + else { + it++; + } + } + brd.update_refs(); + + rebuild(true); + + } + + bool CoreBoard::has_object_type(ObjectType ty) { + switch(ty) { + case ObjectType::JUNCTION: + case ObjectType::POLYGON: + case ObjectType::HOLE: + case ObjectType::TRACK: + case ObjectType::POLYGON_EDGE: + case ObjectType::POLYGON_VERTEX: + case ObjectType::POLYGON_ARC_CENTER: + case ObjectType::TEXT: + case ObjectType::LINE: + case ObjectType::BOARD_PACKAGE: + case ObjectType::VIA: + return true; + break; + default: + ; + } + + return false; + } + + std::map *CoreBoard::get_polygon_map(bool work) { + auto &p = work?brd_work:brd; + return &p.polygons; + } + std::map *CoreBoard::get_hole_map(bool work) { + auto &p = work?brd_work:brd; + return &p.holes; + } + std::map *CoreBoard::get_junction_map(bool work) { + auto &p = work?brd_work:brd; + return &p.junctions; + } + std::map *CoreBoard::get_text_map(bool work) { + auto &p = work?brd_work:brd; + return &p.texts; + } + + bool CoreBoard::get_property_bool(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled) { + bool h = false; + bool r= Core::get_property_bool(uu, type, property, &h); + if(h) + return r; + switch(type) { + case ObjectType::BOARD_PACKAGE : + switch(property) { + case ObjectProperty::ID::FLIPPED : + return brd.packages.at(uu).flip; + default : + return false; + } + break; + case ObjectType::TRACK : + switch(property) { + case ObjectProperty::ID::WIDTH_FROM_NET_CLASS : + return brd.tracks.at(uu).width_from_net_class; + default : + return false; + } + break; + default : + return false; + } + return false; + } + void CoreBoard::set_property_bool(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool value, bool *handled) { + if(tool_is_active()) + return; + bool h = false; + Core::set_property_bool(uu, type, property, value, &h); + if(h) + return; + switch(type) { + case ObjectType::BOARD_PACKAGE : + switch(property) { + case ObjectProperty::ID::FLIPPED : + brd.packages.at(uu).flip = value; + break; + default : + ; + } + break; + case ObjectType::TRACK : + switch(property) { + case ObjectProperty::ID::WIDTH_FROM_NET_CLASS : + brd.tracks.at(uu).width_from_net_class = value; + break; + default : + ; + } + break; + default : + ; + } + rebuild(); + } + int64_t CoreBoard::get_property_int(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled) { + bool h = false; + int64_t r= Core::get_property_int(uu, type, property, &h); + if(h) + return r; + switch(type) { + case ObjectType::TRACK : + switch(property) { + case ObjectProperty::ID::WIDTH : { + auto &tr = brd.tracks.at(uu); + if(tr.width_from_net_class) { + if(tr.net) { + return tr.net->net_class->default_width; + } + else { + return 0; + } + } + else { + return brd.tracks.at(uu).width; + } + } break; + case ObjectProperty::ID::LAYER : + return brd.tracks.at(uu).layer; + default : + return false; + } + break; + default : + return false; + } + return false; + } + void CoreBoard::set_property_int(const UUID &uu, ObjectType type, ObjectProperty::ID property, int64_t value, bool *handled) { + if(tool_is_active()) + return; + bool h = false; + Core::set_property_int(uu, type, property, value, &h); + if(h) + return; + switch(type) { + case ObjectType::TRACK: + switch(property) { + case ObjectProperty::ID::WIDTH : { + auto &tr = brd.tracks.at(uu); + if(tr.width_from_net_class) + return; + value = std::max((int64_t)0, value); + if(tr.net) { + value = std::max((int64_t)tr.net->net_class->min_width, value); + } + + tr.width = value; + } break; + case ObjectProperty::ID::LAYER : + brd.tracks.at(uu).layer = value; + break; + default : + ; + } + break; + default : + ; + } + rebuild(); + } + + std::string CoreBoard::get_property_string(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled) { + bool h = false; + std::string r = Core::get_property_string(uu, type, property, &h); + if(h) + return r; + switch(type) { + case ObjectType::TRACK : + switch(property) { + case ObjectProperty::ID::NAME : + if(brd.tracks.at(uu).net) { + return brd.tracks.at(uu).net->name; + } + return ""; + case ObjectProperty::ID::NET_CLASS : + if(brd.tracks.at(uu).net) { + return brd.tracks.at(uu).net->net_class->name; + } + return ""; + default : + return "meh"; + } + break; + case ObjectType::BOARD_PACKAGE : + switch(property) { + case ObjectProperty::ID::REFDES : + return brd.packages.at(uu).component->refdes; + case ObjectProperty::ID::NAME : + return brd.packages.at(uu).component->part->package->name; + default : + return "meh"; + } + break; + + default : + return "meh"; + } + return "meh"; + } + + bool CoreBoard::property_is_settable(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled) { + bool h = false; + bool r= Core::property_is_settable(uu, type, property, &h); + if(h) + return r; + switch(type) { + case ObjectType::TRACK : + switch(property) { + case ObjectProperty::ID::WIDTH: + return !brd.tracks.at(uu).width_from_net_class; + break; + default : + ; + } + break; + default : + ; + } + return true; + } + + std::vector CoreBoard::get_tracks(bool work) { + auto &p = work?brd_work:brd; + std::vector r; + for(auto &it: p.tracks) { + r.push_back(&it.second); + } + return r; + } + + void CoreBoard::rebuild(bool from_undo) { + brd.expand(); + brd_work = brd; + block_work = block; + brd_work.block = &block_work; + brd_work.update_refs(); + Core::rebuild(from_undo); + } + + const std::map &CoreBoard::get_layers() { + return m_layers; + } + + const Board *CoreBoard::get_canvas_data() { + return &brd_work; + } + + Board *CoreBoard::get_board(bool work) { + return work?&brd_work:&brd; + } + + Constraints *CoreBoard::get_constraints() { + return &constraints; + } + + ViaPadstackProvider *CoreBoard::get_via_padstack_provider() { + return &via_padstack_provider; + } + + void CoreBoard::commit() { + brd = brd_work; + block = block_work; + brd.block = █ + brd_work.block = &block_work; + brd.update_refs(); + brd_work.update_refs(); + } + + void CoreBoard::revert() { + brd_work = brd; + block_work = block; + brd.block = █ + brd_work.block = &block_work; + brd.update_refs(); + brd_work.update_refs(); + reverted = true; + } + + void CoreBoard::history_push() { + history.push_back(std::make_unique(block, brd)); + auto x = dynamic_cast(history.back().get()); + x->brd.block = &x->block; + x->brd.update_refs(); + } + + void CoreBoard::history_load(unsigned int i) { + auto x = dynamic_cast(history.at(history_current).get()); + brd = x->brd; + block = x->block; + brd.block = █ + brd.update_refs(); + rebuild(true); + } + + json CoreBoard::get_meta() { + json j; + std::ifstream ifs(m_board_filename); + if(!ifs.is_open()) { + throw std::runtime_error("file " +m_board_filename+ " not opened"); + } + ifs>>j; + ifs.close(); + if(j.count("_imp")) { + return j["_imp"]; + } + return nullptr; + } + + std::pair CoreBoard::get_bbox() { + Coordi a,b; + bool found = false; + for(const auto &it: brd_work.polygons) { + if(it.second.layer == 50) { //outline + found = true; + for(const auto &v: it.second.vertices) { + a = Coordi::min(a, v.position); + b = Coordi::max(b, v.position); + } + } + } + if(!found) + return {{-10_mm, -10_mm}, {10_mm, 10_mm}}; + return {a,b}; + } + + void CoreBoard::save() { + auto j = brd.serialize(); + auto save_meta = s_signal_request_save_meta.emit(); + j["_imp"] = save_meta; + save_json_to_file(m_board_filename, j); + save_json_to_file(m_block_filename, block.serialize()); + + + //json j = block.serialize(); + //std::cout << std::setw(4) << j << std::endl; + } +} diff --git a/core/core_board.hpp b/core/core_board.hpp new file mode 100644 index 000000000..e9eeba146 --- /dev/null +++ b/core/core_board.hpp @@ -0,0 +1,73 @@ +#pragma once +#include "pool.hpp" +#include "core.hpp" +#include "board.hpp" +#include "block.hpp" +#include "constraints.hpp" +#include +#include + +namespace horizon { + class CoreBoard: public Core { + public: + CoreBoard(const std::string &board_filename, const std::string &block_filename, const std::string &constraints_filename, const std::string &via_dir, Pool &pool); + bool has_object_type(ObjectType ty) override; + + const std::map &get_layers(); + + virtual bool get_property_bool(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled=nullptr) override; + virtual void set_property_bool(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool value, bool *handled=nullptr) override; + virtual int64_t get_property_int(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled=nullptr) override; + virtual void set_property_int(const UUID &uu, ObjectType type, ObjectProperty::ID property, int64_t value, bool *handled=nullptr) override; + virtual std::string get_property_string(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled=nullptr) override; + + virtual bool property_is_settable(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled=nullptr); + + std::vector get_tracks(bool work=true); + + virtual void rebuild(bool from_undo=false); + virtual void commit(); + virtual void revert(); + virtual void save(); + void reload_netlist(); + + const Board *get_canvas_data(); + Board *get_board(bool work = true); + ViaPadstackProvider *get_via_padstack_provider(); + Constraints *get_constraints() override; + std::pair get_bbox() override; + + json get_meta() override; + + private: + std::map *get_polygon_map(bool work=true) override; + std::map *get_hole_map(bool work=true) override; + std::map *get_junction_map(bool work=true) override; + std::map *get_text_map(bool work=true) override; + + Constraints constraints; + ViaPadstackProvider via_padstack_provider; + + Block block; + Block block_work; + + Board brd; + Board brd_work; + + std::string m_board_filename; + std::string m_block_filename; + std::string m_constraints_filename; + std::string m_via_dir; + + std::map m_layers; + + class HistoryItem: public Core::HistoryItem { + public: + HistoryItem(const Block &b, const Board &r):block(b), brd(r) {} + Block block; + Board brd; + }; + void history_push(); + void history_load(unsigned int i); + }; +} diff --git a/core/core_package.cpp b/core/core_package.cpp new file mode 100644 index 000000000..ef05b5485 --- /dev/null +++ b/core/core_package.cpp @@ -0,0 +1,195 @@ +#include "core_package.hpp" +#include +#include +#include "json.hpp" + +namespace horizon { + CorePackage::CorePackage(const std::string &filename, Pool &pool): + package(Package::new_from_file(filename, pool)), + package_work(package), + m_filename(filename) + { + rebuild(); + m_pool = &pool; + } + + static const std::map layers = { + {40, {40, "Top Courtyard", {.5,.5,.5}}}, + {30, {30, "Top Placement", {.5,.5,.5}}}, + {20, {20, "Top Silkscreen", {.9,.9,.9}}}, + {10, {10, "Top Mask", {1,.5,.5}}}, + {0, {0, "Top Copper", {1,0,0}}}, + {-1, {-1, "Inner", {1,1,0}}}, + {-100, {-100, "Bottom Copper", {0,1,0}, true}}, + {-110, {-110, "Bottom Mask", {.5,1,.5}, true}}, + {-120, {-120, "Bottom Silkscreen", {.9,.9,.9}, true}}, + {-130, {-130, "Bottom Placement", {.5,.5,.5}}}, + {-140, {-140, "Bottom Courtyard", {.5,.5,.5}}}, + + }; + + bool CorePackage::has_object_type(ObjectType ty) { + switch(ty) { + case ObjectType::JUNCTION: + case ObjectType::POLYGON: + case ObjectType::POLYGON_EDGE: + case ObjectType::POLYGON_VERTEX: + case ObjectType::POLYGON_ARC_CENTER: + case ObjectType::PAD: + case ObjectType::TEXT: + case ObjectType::LINE: + + return true; + break; + default: + ; + } + + return false; + } + + const std::map &CorePackage::get_layers() { + return layers; + } + + Package *CorePackage::get_package(bool work) { + return work?&package_work:&package; + } + + std::map *CorePackage::get_junction_map(bool work) { + auto &p = work?package_work:package; + return &p.junctions; + } + std::map *CorePackage::get_line_map(bool work) { + auto &p = work?package_work:package; + return &p.lines; + } + std::map *CorePackage::get_arc_map(bool work) { + auto &p = work?package_work:package; + return &p.arcs; + } + std::map *CorePackage::get_text_map(bool work) { + auto &p = work?package_work:package; + return &p.texts; + } + std::map *CorePackage::get_polygon_map(bool work) { + auto &p = work?package_work:package; + return &p.polygons; + } + std::map *CorePackage::get_hole_map(bool work) { + auto &p = work?package_work:package; + //return &p.holes; + return nullptr; + } + + std::pair CorePackage::get_bbox() { + auto bb = package_work.get_bbox(); + int64_t pad = 1_mm; + bb.first.x -= pad; + bb.first.y -= pad; + + bb.second.x += pad; + bb.second.y += pad; + return bb; + } + + std::string CorePackage::get_property_string(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled) { + bool h = false; + std::string r= Core::get_property_string(uu, type, property, &h); + if(h) + return r; + switch(type) { + case ObjectType::PAD : + switch(property) { + case ObjectProperty::ID::NAME : + return package.pads.at(uu).name; + default : + return ""; + } + break; + default : + return ""; + } + return ""; + } + void CorePackage::set_property_string(const UUID &uu, ObjectType type, ObjectProperty::ID property, const std::string &value, bool *handled) { + if(tool_is_active()) + return; + bool h = false; + Core::set_property_string(uu, type, property, value, &h); + if(h) + return; + switch(type) { + case ObjectType::PAD : + switch(property) { + case ObjectProperty::ID::NAME : + package.pads.at(uu).name = value; + break; + default : + ; + } + break; + default : + ; + } + rebuild(); + } + + void CorePackage::rebuild(bool from_undo) { + package_work = package; + Core::rebuild(from_undo); + } + + void CorePackage::history_push() { + history.push_back(std::make_unique(package)); + } + + void CorePackage::history_load(unsigned int i) { + auto x = dynamic_cast(history.at(history_current).get()); + package = x->package; + rebuild(true); + } + + const Package *CorePackage::get_canvas_data() { + return &package_work; + } + + void CorePackage::commit() { + package = package_work; + } + + void CorePackage::revert() { + package_work = package; + reverted = true; + } + + json CorePackage::get_meta() { + json j; + std::ifstream ifs(m_filename); + if(!ifs.is_open()) { + throw std::runtime_error("file " +m_filename+ " not opened"); + } + ifs>>j; + ifs.close(); + if(j.count("_imp")) { + return j["_imp"]; + } + return nullptr; + } + + void CorePackage::save() { + s_signal_save.emit(); + + std::ofstream ofs(m_filename); + if(!ofs.is_open()) { + std::cout << "can't save symbol" < +#include +#include + +namespace horizon { + class CorePackage: public Core { + public: + CorePackage(const std::string &filename, Pool &pool); + bool has_object_type(ObjectType ty) override; + + Package *get_package(bool work=true); + + /*Polygon *insert_polygon(const UUID &uu, bool work = true); + Polygon *get_polygon(const UUID &uu, bool work=true); + void delete_polygon(const UUID &uu, bool work = true); + Hole *insert_hole(const UUID &uu, bool work = true); + Hole *get_hole(const UUID &uu, bool work=true); + void delete_hole(const UUID &uu, bool work = true);*/ + + const std::map &get_layers(); + + virtual std::string get_property_string(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled=nullptr); + virtual void set_property_string(const UUID &uu, ObjectType type, ObjectProperty::ID property, const std::string &value, bool *handled=nullptr); + + void rebuild(bool from_undo=false); + void commit(); + void revert(); + void save(); + + + const Package *get_canvas_data(); + std::pair get_bbox() override; + json get_meta() override; + + private: + std::map *get_junction_map(bool work=true) override; + std::map *get_line_map(bool work=true) override; + std::map *get_arc_map(bool work=true) override; + std::map *get_text_map(bool work=true) override; + std::map *get_polygon_map(bool work=true) override; + std::map *get_hole_map(bool work=true) override; + + + + Package package; + Package package_work; + std::string m_filename; + + class HistoryItem: public Core::HistoryItem { + public: + HistoryItem(const Package &k):package(k) {} + Package package; + }; + void history_push(); + void history_load(unsigned int i); + }; +} diff --git a/core/core_padstack.cpp b/core/core_padstack.cpp new file mode 100644 index 000000000..42eaf72b3 --- /dev/null +++ b/core/core_padstack.cpp @@ -0,0 +1,108 @@ +#include "core_padstack.hpp" +#include +#include +#include "json.hpp" + +namespace horizon { + CorePadstack::CorePadstack(const std::string &filename, Pool &pool): + padstack(Padstack::new_from_file(filename)), + padstack_work(padstack), + m_filename(filename) + { + rebuild(); + m_pool = &pool; + } + + static const std::map layers = { + {10, {10, "Top Mask", {1,.5,.5}}}, + {0, {0, "Top Copper", {1,0,0}}}, + {-1, {-1, "Inner", {1,1,0}}}, + {-100, {-100, "Bottom Copper", {0,1,0}}}, + {-110, {-110, "Bottom Mask", {.5,1,.5}}} + }; + + bool CorePadstack::has_object_type(ObjectType ty) { + switch(ty) { + case ObjectType::HOLE: + case ObjectType::POLYGON: + case ObjectType::POLYGON_VERTEX: + case ObjectType::POLYGON_EDGE: + case ObjectType::POLYGON_ARC_CENTER: + return true; + break; + default: + ; + } + + return false; + } + + const std::map &CorePadstack::get_layers() { + return layers; + } + + std::map *CorePadstack::get_polygon_map(bool work) { + auto &p = work?padstack_work:padstack; + return &p.polygons; + } + std::map *CorePadstack::get_hole_map(bool work) { + auto &p = work?padstack_work:padstack; + return &p.holes; + } + + void CorePadstack::rebuild(bool from_undo) { + padstack_work = padstack; + Core::rebuild(from_undo); + } + + void CorePadstack::history_push() { + history.push_back(std::make_unique(padstack)); + } + + void CorePadstack::history_load(unsigned int i) { + auto x = dynamic_cast(history.at(history_current).get()); + padstack = x->padstack; + rebuild(true); + } + + const Padstack *CorePadstack::get_canvas_data() { + return &padstack_work; + } + + Padstack *CorePadstack::get_padstack(bool work) { + return work?&padstack_work:&padstack; + } + + std::pair CorePadstack::get_bbox() { + auto bb = padstack_work.get_bbox(); + int64_t pad = 1_mm; + bb.first.x -= pad; + bb.first.y -= pad; + + bb.second.x += pad; + bb.second.y += pad; + return bb; + } + + void CorePadstack::commit() { + padstack = padstack_work; + } + + void CorePadstack::revert() { + padstack_work = padstack; + reverted = true; + } + + void CorePadstack::save() { + s_signal_save.emit(); + + std::ofstream ofs(m_filename); + if(!ofs.is_open()) { + std::cout << "can't save symbol" < +#include +#include + +namespace horizon { + class CorePadstack: public Core { + public: + CorePadstack(const std::string &filename, Pool &pool); + bool has_object_type(ObjectType ty) override; + + const std::map &get_layers(); + + void rebuild(bool from_undo=false); + void commit(); + void revert(); + void save(); + + Padstack *get_padstack(bool work = true); + + const Padstack *get_canvas_data(); + std::pair get_bbox() override; + + private: + std::map *get_polygon_map(bool work=true) override; + std::map *get_hole_map(bool work=true) override; + + Padstack padstack; + Padstack padstack_work; + std::string m_filename; + + class HistoryItem: public Core::HistoryItem { + public: + HistoryItem(const Padstack &s):padstack(s) {} + Padstack padstack; + }; + void history_push(); + void history_load(unsigned int i); + }; +} diff --git a/core/core_properties.cpp b/core/core_properties.cpp new file mode 100644 index 000000000..83f3913cc --- /dev/null +++ b/core/core_properties.cpp @@ -0,0 +1,244 @@ +#include "core.hpp" + +namespace horizon { + #define HANDLED if(handled){*handled=true;} + #define NOT_HANDLED if(handled){*handled=false;} + + bool Core::get_property_bool(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled) { + switch(type) { + case ObjectType::HOLE : + switch(property) { + case ObjectProperty::ID::PLATED : + HANDLED + return get_hole(uu)->plated; + default : + ; + } + break; + default : + ; + } + NOT_HANDLED + return false; + } + void Core::set_property_bool(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool value, bool *handled) { + switch(type) { + case ObjectType::HOLE : + switch(property) { + case ObjectProperty::ID::PLATED : + HANDLED + if(get_hole(uu, false)->plated == value) + return; + get_hole(uu, false)->plated = value; + break; + default : + NOT_HANDLED + } + break; + + + default: + NOT_HANDLED + } + rebuild(); + } + + int64_t Core::get_property_int(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled) { + switch(type) { + case ObjectType::LINE : + switch(property) { + case ObjectProperty::ID::WIDTH: + HANDLED + return get_line(uu, false)->width; + case ObjectProperty::ID::LAYER: + HANDLED + return get_line(uu, false)->layer; + default : + NOT_HANDLED + return 0; + } + break; + case ObjectType::ARC : + switch(property) { + case ObjectProperty::ID::WIDTH: + HANDLED + return get_arc(uu, false)->width; + case ObjectProperty::ID::LAYER: + HANDLED + return get_arc(uu, false)->layer; + default : + NOT_HANDLED + return 0; + } + break; + case ObjectType::TEXT : + switch(property) { + case ObjectProperty::ID::WIDTH: + HANDLED + return get_text(uu, false)->width; + case ObjectProperty::ID::SIZE: + HANDLED + return get_text(uu, false)->size; + case ObjectProperty::ID::LAYER: + HANDLED + return get_text(uu, false)->layer; + default : + NOT_HANDLED + return 0; + } + break; + case ObjectType::POLYGON : + switch(property) { + case ObjectProperty::ID::LAYER: + HANDLED + return get_polygon(uu, false)->layer; + default : + NOT_HANDLED + return 0; + } + break; + case ObjectType::HOLE : + switch(property) { + case ObjectProperty::ID::DIAMETER: + HANDLED + return get_hole(uu, false)->diameter; + default : + NOT_HANDLED + return 0; + } + break; + + default : + NOT_HANDLED + return 0; + } + } + void Core::set_property_int(const UUID &uu, ObjectType type, ObjectProperty::ID property, int64_t value, bool *handled) { + if(tool_is_active()) + return; + int64_t newv; + switch(type) { + case ObjectType::LINE : + switch(property) { + case ObjectProperty::ID::WIDTH: + HANDLED + get_line(uu, false)->width = std::max(value, (int64_t)0); + break; + case ObjectProperty::ID::LAYER: + HANDLED + get_line(uu, false)->layer = value; + break; + default : + NOT_HANDLED + return; + } + break; + case ObjectType::ARC : + switch(property) { + case ObjectProperty::ID::WIDTH: + HANDLED + get_arc(uu, false)->width = std::max(value, (int64_t)0); + break; + case ObjectProperty::ID::LAYER: + HANDLED + get_arc(uu, false)->layer = value; + break; + default : + NOT_HANDLED + return; + } + break; + case ObjectType::TEXT : + switch(property) { + case ObjectProperty::ID::SIZE: + HANDLED + get_text(uu, false)->size = std::max(value, (int64_t)500000); + break; + case ObjectProperty::ID::WIDTH: + HANDLED + get_text(uu, false)->width = std::max(value, (int64_t)0); + break; + case ObjectProperty::ID::LAYER: + HANDLED + get_text(uu, false)->layer = value; + break; + default : + NOT_HANDLED + return; + } + break; + case ObjectType::POLYGON : + switch(property) { + case ObjectProperty::ID::LAYER: + HANDLED + newv = value; + if(get_layers().count(newv) == 0) + return; + if(newv == (int64_t)get_polygon(uu, false)->layer) + return; + get_polygon(uu, false)->layer = newv; + break; + default : + NOT_HANDLED + } + break; + case ObjectType::HOLE : + switch(property) { + case ObjectProperty::ID::DIAMETER: + HANDLED + newv = std::max(value, 0.01_mm); + if(newv == (int64_t)get_hole(uu, false)->diameter) + return; + get_hole(uu, false)->diameter = newv; + break; + default : + NOT_HANDLED + } + break; + + default : + NOT_HANDLED + return; + } + rebuild(); + } + + std::string Core::get_property_string(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled) { + switch(type) { + case ObjectType::TEXT : + switch(property) { + case ObjectProperty::ID::TEXT : + HANDLED + return get_text(uu, false)->text; + break; + default : + NOT_HANDLED + } + break; + default: + NOT_HANDLED + } + return ""; + } + + void Core::set_property_string(const UUID &uu, ObjectType type, ObjectProperty::ID property, const std::string &value, bool *handled) { + switch(type) { + case ObjectType::TEXT : + switch(property) { + case ObjectProperty::ID::TEXT : + HANDLED + get_text(uu, false)->text = value; + break; + default : + NOT_HANDLED + return; + } + break; + default: + NOT_HANDLED + return; + } + rebuild(); + } + +} diff --git a/core/core_schematic.cpp b/core/core_schematic.cpp new file mode 100644 index 000000000..835d8b9e1 --- /dev/null +++ b/core/core_schematic.cpp @@ -0,0 +1,516 @@ +#include "core_schematic.hpp" +#include +#include "json.hpp" +#include "part.hpp" +#include "util.hpp" + +namespace horizon { + CoreSchematic::CoreSchematic(const std::string &schematic_filename, const std::string &block_filename, const std::string &constraints_filename, Pool &pool) : + constraints(Constraints::new_from_file(constraints_filename)), + block(Block::new_from_file(block_filename, pool, &constraints)), + block_work(block), + sch(Schematic::new_from_file(schematic_filename, block, pool)), + sch_work(sch), + m_schematic_filename(schematic_filename), + m_block_filename(block_filename), + m_constraints_filename(constraints_filename) + { + auto x = std::find_if(sch.sheets.cbegin(), sch.sheets.cend(), [](const auto &a){return a.second.index == 1;}); + assert(x!=sch.sheets.cend()); + sheet_uuid = x->first; + m_pool = &pool; + sch.block = █ + sch_work.block = &block_work; + sch.update_refs(); + sch_work.update_refs(); + rebuild(); + } + + Junction *CoreSchematic::get_junction(const UUID &uu, bool work) { + auto &s = work?sch_work:sch; + auto &sheet = s.sheets.at(sheet_uuid); + return &sheet.junctions.at(uu); + } + SchematicSymbol *CoreSchematic::get_schematic_symbol(const UUID &uu, bool work) { + auto &s = work?sch_work:sch; + auto &sheet = s.sheets.at(sheet_uuid); + return &sheet.symbols.at(uu); + } + Text *CoreSchematic::get_text(const UUID &uu, bool work) { + auto &s = work?sch_work:sch; + auto &sheet = s.sheets.at(sheet_uuid); + return &sheet.texts.at(uu); + } + Schematic *CoreSchematic::get_schematic(bool work) { + return work?&sch_work:&sch; + } + Sheet *CoreSchematic::get_sheet(bool work) { + auto &s = work?sch_work:sch; + return &s.sheets.at(sheet_uuid); + } + Line *CoreSchematic::get_line(const UUID &uu, bool work) { + return nullptr; + } + Arc *CoreSchematic::get_arc(const UUID &uu, bool work) { + return nullptr; + } + + Junction *CoreSchematic::insert_junction(const UUID &uu, bool work) { + auto &s = work?sch_work:sch; + auto &sheet = s.sheets.at(sheet_uuid); + auto x = sheet.junctions.emplace(std::make_pair(uu, uu)); + return &(x.first->second); + } + + LineNet *CoreSchematic::insert_line_net(const UUID &uu, bool work) { + auto &s = work?sch_work:sch; + auto &sheet = s.sheets.at(sheet_uuid); + auto x = sheet.net_lines.emplace(std::make_pair(uu, uu)); + return &(x.first->second); + } + + void CoreSchematic::delete_junction(const UUID &uu, bool work) { + auto &s = work?sch_work:sch; + auto &sheet = s.sheets.at(sheet_uuid); + sheet.junctions.erase(uu); + } + void CoreSchematic::delete_line_net(const UUID &uu, bool work) { + auto &s = work?sch_work:sch; + auto &sheet = s.sheets.at(sheet_uuid); + sheet.net_lines.erase(uu); + } + void CoreSchematic::delete_schematic_symbol(const UUID &uu, bool work) { + auto &s = work?sch_work:sch; + auto &sheet = s.sheets.at(sheet_uuid); + sheet.symbols.erase(uu); + } + SchematicSymbol *CoreSchematic::insert_schematic_symbol(const UUID &uu, Symbol *sym, bool work) { + auto &s = work?sch_work:sch; + auto &sheet = s.sheets.at(sheet_uuid); + auto x = sheet.symbols.emplace(std::make_pair(uu, SchematicSymbol{uu, sym})); + return &(x.first->second); + return nullptr; + } + + Line *CoreSchematic::insert_line(const UUID &uu, bool work) { + return nullptr; + } + void CoreSchematic::delete_line(const UUID &uu, bool work) { + return; + } + + Arc *CoreSchematic::insert_arc(const UUID &uu, bool work) { + return nullptr; + } + void CoreSchematic::delete_arc(const UUID &uu, bool work) { + return; + } + + std::vector CoreSchematic::get_net_lines(bool work) { + auto &s = work?sch_work:sch; + auto &sheet = s.sheets.at(sheet_uuid); + std::vector r; + for(auto &it: sheet.net_lines) { + r.push_back(&it.second); + } + return r; + } + std::vector CoreSchematic::get_net_labels(bool work) { + auto &s = work?sch_work:sch; + auto &sheet = s.sheets.at(sheet_uuid); + std::vector r; + for(auto &it: sheet.net_labels) { + r.push_back(&it.second); + } + return r; + } + + std::vector CoreSchematic::get_lines(bool work) { + std::vector r; + return r; + } + + std::vector CoreSchematic::get_arcs(bool work) { + std::vector r; + return r; + } + + void CoreSchematic::delete_text(const UUID &uu, bool work) { + auto &s = work?sch_work:sch; + auto &sheet = s.sheets.at(sheet_uuid); + sheet.texts.erase(uu); + } + Text *CoreSchematic::insert_text(const UUID &uu, bool work) { + auto &s = work?sch_work:sch; + auto &sheet = s.sheets.at(sheet_uuid); + auto x = sheet.texts.emplace(std::make_pair(uu, uu)); + return &(x.first->second); + } + + bool CoreSchematic::has_object_type(ObjectType ty) { + switch(ty) { + case ObjectType::JUNCTION: + case ObjectType::SCHEMATIC_SYMBOL: + case ObjectType::BUS_LABEL: + case ObjectType::BUS_RIPPER: + case ObjectType::NET_LABEL: + case ObjectType::LINE_NET: + case ObjectType::POWER_SYMBOL: + return true; + break; + default: + ; + } + + return false; + } + + + std::string CoreSchematic::get_property_string(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled) { + bool h = false; + std::string r= Core::get_property_string(uu, type, property, &h); + if(h) + return r; + auto &sheet = sch.sheets.at(sheet_uuid); + switch(type) { + case ObjectType::NET : + switch(property) { + case ObjectProperty::ID::NAME : + return block.nets.at(uu).name; + case ObjectProperty::ID::NET_CLASS : + return block.nets.at(uu).net_class->uuid; + default : + return ""; + } + break; + case ObjectType::NET_LABEL : + switch(property) { + case ObjectProperty::ID::NAME : + if(sheet.net_labels.at(uu).junction->net) + return sheet.net_labels.at(uu).junction->net->name; + else + return ""; + default : + return ""; + } + break; + case ObjectType::COMPONENT : + switch(property) { + case ObjectProperty::ID::REFDES : + return block.components.at(uu).refdes; + case ObjectProperty::ID::VALUE : + if(block.components.at(uu).part) + return block.components.at(uu).part->get_value(); + else + return block.components.at(uu).value; + case ObjectProperty::ID::MPN : + if(block.components.at(uu).part) + return block.components.at(uu).part->get_MPN(); + else + return ""; + default : + return ""; + } + break; + + default : + return ""; + } + return ""; + } + void CoreSchematic::set_property_string(const UUID &uu, ObjectType type, ObjectProperty::ID property, const std::string &value, bool *handled) { + if(tool_is_active()) + return; + bool h = false; + Core::set_property_string(uu, type, property, value, &h); + if(h) + return; + switch(type) { + case ObjectType::COMPONENT : + switch(property) { + case ObjectProperty::ID::REFDES : + block.components.at(uu).refdes = value; + break; + case ObjectProperty::ID::VALUE : + if((block.components.at(uu).part)) + return; + block.components.at(uu).value = value; + break; + default : + ; + } + break; + case ObjectType::NET : + switch(property) { + case ObjectProperty::ID::NAME : + block.nets.at(uu).name = value; + break; + case ObjectProperty::ID::NET_CLASS : { + UUID nc = value; + block.nets.at(uu).net_class = &constraints.net_classes.at(nc); + }break; + default : + ; + } + break; + + default : + ; + } + rebuild(); + } + + void CoreSchematic::set_property_bool(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool value, bool *handled) { + if(tool_is_active()) + return; + bool h = false; + Core::set_property_bool(uu, type, property, value, &h); + if(h) + return; + auto &sheet = sch.sheets.at(sheet_uuid); + switch(type) { + case ObjectType::NET : + switch(property) { + case ObjectProperty::ID::IS_POWER : + if(block.nets.at(uu).is_power_forced) + return; + if(block.nets.at(uu).is_power == value) + return; + block.nets.at(uu).is_power = value; + break; + default : + ; + } + break; + case ObjectType::NET_LABEL : + switch(property) { + case ObjectProperty::ID::OFFSHEET_REFS : + if(sheet.net_labels.at(uu).offsheet_refs == value) + return; + sheet.net_labels.at(uu).offsheet_refs = value; + break; + default : + ; + } + break; + + default: + ; + } + rebuild(); + } + + bool CoreSchematic::get_property_bool(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled) { + bool h = false; + bool r= Core::get_property_bool(uu, type, property, &h); + if(h) + return r; + auto &sheet = sch.sheets.at(sheet_uuid); + switch(type) { + case ObjectType::NET : + switch(property) { + case ObjectProperty::ID::IS_POWER : + return block.nets.at(uu).is_power; + default : + ; + } + break; + case ObjectType::NET_LABEL : + switch(property) { + case ObjectProperty::ID::OFFSHEET_REFS : + return sheet.net_labels.at(uu).offsheet_refs; + default : + ; + } + break; + + default : + ; + } + return false; + } + int64_t CoreSchematic::get_property_int(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled) { + bool h = false; + int64_t r= Core::get_property_int(uu, type, property, &h); + if(h) + return r; + auto &sheet = sch.sheets.at(sheet_uuid); + switch(type) { + case ObjectType::NET_LABEL : + switch(property) { + case ObjectProperty::ID::SIZE: + return sheet.net_labels.at(uu).size; + default : + return 0; + } + break; + + default : + return 0; + } + } + void CoreSchematic::set_property_int(const UUID &uu, ObjectType type, ObjectProperty::ID property, int64_t value, bool *handled) { + if(tool_is_active()) + return; + bool h = false; + Core::set_property_int(uu, type, property, value, &h); + if(h) + return; + auto &sheet = sch.sheets.at(sheet_uuid); + int64_t newv; + switch(type) { + case ObjectType::NET_LABEL : + switch(property) { + case ObjectProperty::ID::SIZE: + newv = std::max(value, (int64_t)500000); + if(newv == (int64_t)sheet.net_labels.at(uu).size) + return; + sheet.net_labels.at(uu).size = newv; + break; + default : + ; + } + break; + + default : + ; + } + rebuild(); + } + + bool CoreSchematic::property_is_settable(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled) { + bool h = false; + bool r= Core::property_is_settable(uu, type, property, &h); + if(h) + return r; + switch(type) { + case ObjectType::NET : + switch(property) { + case ObjectProperty::ID::IS_POWER: + return !block.nets.at(uu).is_power_forced; + break; + default : + ; + } + break; + case ObjectType::COMPONENT : + switch(property) { + case ObjectProperty::ID::VALUE: + return block.components.at(uu).part==nullptr; + break; + default : + ; + } + break; + + default : + ; + } + return true; + } + + Constraints *CoreSchematic::get_constraints() { + return &constraints; + } + + void CoreSchematic::add_sheet() { + auto uu = UUID::random(); + auto sheet_max = std::max_element(sch.sheets.begin(), sch.sheets.end(), + [](const auto &p1, const auto &p2) {return p1.second.index < p2.second.index; } + ); + auto *sheet = &sch.sheets.emplace(uu, uu).first->second; + sheet->index = sheet_max->second.index+1; + sheet->name = "sheet "+std::to_string(sheet->index); + rebuild(); + } + + void CoreSchematic::delete_sheet(const UUID &uu) { + if(sch.sheets.size() <= 1) + return; + if(sch.sheets.at(uu).symbols.size()>0) //only delete empty sheets + return; + auto deleted_index = sch.sheets.at(uu).index; + sch.sheets.erase(uu); + for(auto &it: sch.sheets) { + if(it.second.index > deleted_index) { + it.second.index--; + } + } + if(sheet_uuid == uu) {//deleted current sheet + auto x = std::find_if(sch.sheets.begin(), sch.sheets.end(), [](auto e){return e.second.index == 1;}); + sheet_uuid = x->first; + } + rebuild(); + } + + void CoreSchematic::set_sheet(const UUID &uu) { + if(tool_is_active()) + return; + if(sch.sheets.count(uu) == 0) + return; + sheet_uuid = uu; + } + + void CoreSchematic::rebuild(bool from_undo) { + sch.expand(); + sch_work = sch; + block_work = block; + sch_work.block = &block_work; + sch_work.update_refs(); + sch.update_refs(); + Core::rebuild(from_undo); + } + + const Sheet *CoreSchematic::get_canvas_data() { + return &sch_work.sheets.at(sheet_uuid); + } + + void CoreSchematic::commit() { + sch = sch_work; + block = block_work; + sch.block = █ + sch_work.block = &block_work; + sch.update_refs(); + sch_work.update_refs(); + } + + void CoreSchematic::revert() { + sch_work = sch; + block_work = block; + sch.block = █ + sch_work.block = &block_work; + sch.update_refs(); + sch_work.update_refs(); + reverted = true; + } + + void CoreSchematic::history_push() { + history.push_back(std::make_unique(block, sch)); + auto x = dynamic_cast(history.back().get()); + x->sch.block = &x->block; + x->sch.update_refs(); + } + + void CoreSchematic::history_load(unsigned int i) { + auto x = dynamic_cast(history.at(history_current).get()); + sch = x->sch; + block = x->block; + sch.block = █ + sch.update_refs(); + rebuild(true); + } + + + std::pair CoreSchematic::get_bbox() { + return get_sheet()->frame.get_bbox(); + } + + void CoreSchematic::save() { + save_json_to_file(m_schematic_filename, sch.serialize()); + save_json_to_file(m_block_filename, block.serialize()); + + + //json j = block.serialize(); + //std::cout << std::setw(4) << j << std::endl; + } +} diff --git a/core/core_schematic.hpp b/core/core_schematic.hpp new file mode 100644 index 000000000..d2f2e0b5f --- /dev/null +++ b/core/core_schematic.hpp @@ -0,0 +1,92 @@ +#pragma once +#include "symbol.hpp" +#include "pool.hpp" +#include "core.hpp" +#include "schematic.hpp" +#include "block.hpp" +#include "constraints.hpp" +#include +#include + +namespace horizon { + class CoreSchematic: public Core { + public: + CoreSchematic(const std::string &schematic_filename, const std::string &block_filename, const std::string &constraints_filename, Pool &pool); + bool has_object_type(ObjectType ty) override; + + virtual Junction *get_junction(const UUID &uu, bool work = true); + virtual Line *get_line(const UUID &uu, bool work = true); + virtual Arc *get_arc(const UUID &uu, bool work = true); + virtual SchematicSymbol *get_schematic_symbol(const UUID &uu, bool work=true); + virtual Schematic *get_schematic(bool work=true); + virtual Sheet *get_sheet(bool work=true); + virtual Text *get_text(const UUID &uu, bool work=true); + + virtual Junction *insert_junction(const UUID &uu, bool work = true); + virtual void delete_junction(const UUID &uu, bool work = true); + virtual Line *insert_line(const UUID &uu, bool work = true); + virtual void delete_line(const UUID &uu, bool work = true); + virtual Arc *insert_arc(const UUID &uu, bool work = true); + virtual void delete_arc(const UUID &uu, bool work = true); + virtual SchematicSymbol *insert_schematic_symbol(const UUID &uu, Symbol *sym, bool work = true); + virtual void delete_schematic_symbol(const UUID &uu, bool work = true); + + virtual LineNet *insert_line_net(const UUID &uu, bool work = true); + virtual void delete_line_net(const UUID &uu, bool work = true); + + virtual Text *insert_text(const UUID &uu, bool work = true); + virtual void delete_text(const UUID &uu, bool work = true); + + virtual std::vector get_lines(bool work = true); + virtual std::vector get_arcs(bool work = true); + virtual std::vector get_net_lines(bool work=true); + virtual std::vector get_net_labels(bool work=true); + + virtual bool property_is_settable(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled=nullptr); + + virtual std::string get_property_string(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled=nullptr); + virtual void set_property_string(const UUID &uu, ObjectType type, ObjectProperty::ID property, const std::string &value, bool *handled=nullptr); + + virtual int64_t get_property_int(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled=nullptr); + virtual void set_property_int(const UUID &uu, ObjectType type, ObjectProperty::ID property, int64_t value, bool *handled=nullptr); + + virtual bool get_property_bool(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled=nullptr); + virtual void set_property_bool(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool value, bool *handled=nullptr); + + virtual void rebuild(bool from_undo=false); + virtual void commit(); + virtual void revert(); + virtual void save(); + + void add_sheet(); + void delete_sheet(const UUID &uu); + + Constraints *get_constraints() override; + + void set_sheet(const UUID &uu); + const Sheet *get_canvas_data(); + std::pair get_bbox() override; + + private: + Constraints constraints; + Block block; + Block block_work; + + Schematic sch; + Schematic sch_work; + + UUID sheet_uuid; + std::string m_schematic_filename; + std::string m_block_filename; + std::string m_constraints_filename; + + class HistoryItem: public Core::HistoryItem { + public: + HistoryItem(const Block &b, const Schematic &s):block(b), sch(s) {} + Block block; + Schematic sch; + }; + void history_push(); + void history_load(unsigned int i); + }; +} diff --git a/core/core_symbol.cpp b/core/core_symbol.cpp new file mode 100644 index 000000000..5bf9de644 --- /dev/null +++ b/core/core_symbol.cpp @@ -0,0 +1,292 @@ +#include "core_symbol.hpp" +#include +#include +#include "json.hpp" + +namespace horizon { + CoreSymbol::CoreSymbol(const std::string &filename, Pool &pool): + sym(Symbol::new_from_file(filename, pool)), + sym_work(sym), + m_filename(filename) + { + rebuild(); + m_pool = &pool; + } + + bool CoreSymbol::has_object_type(ObjectType ty) { + switch(ty) { + case ObjectType::JUNCTION: + case ObjectType::LINE: + case ObjectType::ARC: + case ObjectType::SYMBOL_PIN: + case ObjectType::TEXT: + return true; + break; + default: + ; + } + + return false; + } + + std::map *CoreSymbol::get_text_map(bool work) { + auto &s = work?sym_work:sym; + return &s.texts; + } + + + Junction *CoreSymbol::get_junction(const UUID &uu, bool work) { + auto &s = work?sym_work:sym; + return s.get_junction(uu); + } + Line *CoreSymbol::get_line(const UUID &uu, bool work) { + auto &s = work?sym_work:sym; + return &s.lines.at(uu); + } + SymbolPin *CoreSymbol::get_symbol_pin(const UUID &uu, bool work) { + auto &s = work?sym_work:sym; + return s.get_symbol_pin(uu); + } + Arc *CoreSymbol::get_arc(const UUID &uu, bool work) { + auto &s = work?sym_work:sym; + return &s.arcs.at(uu); + } + + Junction *CoreSymbol::insert_junction(const UUID &uu, bool work) { + auto &s = work?sym_work:sym; + auto x = s.junctions.emplace(std::make_pair(uu, uu)); + return &(x.first->second); + } + void CoreSymbol::delete_junction(const UUID &uu, bool work) { + auto &s = work?sym_work:sym; + s.junctions.erase(uu); + } + + Line *CoreSymbol::insert_line(const UUID &uu, bool work) { + auto &s = work?sym_work:sym; + auto x = s.lines.emplace(std::make_pair(uu, uu)); + return &(x.first->second); + } + void CoreSymbol::delete_line(const UUID &uu, bool work) { + auto &s = work?sym_work:sym; + s.lines.erase(uu); + } + + Arc *CoreSymbol::insert_arc(const UUID &uu, bool work) { + auto &s = work?sym_work:sym; + auto x = s.arcs.emplace(std::make_pair(uu, uu)); + return &(x.first->second); + } + void CoreSymbol::delete_arc(const UUID &uu, bool work) { + auto &s = work?sym_work:sym; + s.arcs.erase(uu); + } + + void CoreSymbol::delete_symbol_pin(const UUID &uu, bool work) { + auto &s = work?sym_work:sym; + s.pins.erase(uu); + } + SymbolPin *CoreSymbol::insert_symbol_pin(const UUID &uu, bool work) { + auto &s = work?sym_work:sym; + auto x = s.pins.emplace(std::make_pair(uu, uu)); + return &(x.first->second); + } + + std::vector CoreSymbol::get_lines(bool work) { + auto &s = work?sym_work:sym; + std::vector r; + for(auto &it: s.lines) { + r.push_back(&it.second); + } + return r; + } + + std::vector CoreSymbol::get_arcs(bool work) { + auto &s = work?sym_work:sym; + std::vector r; + for(auto &it: s.arcs) { + r.push_back(&it.second); + } + return r; + } + + std::vector CoreSymbol::get_pins(bool work) { + auto &s = work?sym_work:sym; + std::vector r; + for(auto &it: s.unit->pins) { + r.push_back(&it.second); + } + return r; + } + + std::string CoreSymbol::get_property_string(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled) { + bool h = false; + std::string r= Core::get_property_string(uu, type, property, &h); + if(h) + return r; + switch(type) { + case ObjectType::SYMBOL_PIN : + switch(property) { + case ObjectProperty::ID::NAME : + return sym.unit->pins.at(uu).primary_name; + default : + return ""; + } + break; + + default : + return ""; + } + return ""; + } + + bool CoreSymbol::get_property_bool(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled) { + bool h = false; + bool r= Core::get_property_bool(uu, type, property, &h); + if(h) + return r; + + switch(type) { + case ObjectType::SYMBOL_PIN : + switch(property) { + case ObjectProperty::ID::NAME_VISIBLE : + return sym.pins.at(uu).name_visible; + case ObjectProperty::ID::PAD_VISIBLE : + return sym.pins.at(uu).pad_visible; + default : + return false; + } + break; + + default : + return false; + } + } + + + void CoreSymbol::set_property_bool(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool value, bool *handled) { + if(tool_is_active()) + return; + bool h = false; + Core::set_property_bool(uu, type, property, value, &h); + if(h) + return; + switch(type) { + case ObjectType::SYMBOL_PIN : + switch(property) { + case ObjectProperty::ID::NAME_VISIBLE : + sym.pins.at(uu).name_visible = value; + break; + case ObjectProperty::ID::PAD_VISIBLE : + sym.pins.at(uu).pad_visible = value; + break; + default : + ; + } + break; + + default : + ; + } + rebuild(); + } + int64_t CoreSymbol::get_property_int(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled) { + bool h = false; + int64_t r= Core::get_property_int(uu, type, property, &h); + if(h) + return r; + switch(type) { + case ObjectType::SYMBOL_PIN : + switch(property) { + case ObjectProperty::ID::LENGTH: + return sym.pins.at(uu).length; + default : + return 0; + } + break; + + default : + return 0; + } + } + void CoreSymbol::set_property_int(const UUID &uu, ObjectType type, ObjectProperty::ID property, int64_t value, bool *handled) { + if(tool_is_active()) + return; + bool h = false; + Core::set_property_int(uu, type, property, value, &h); + if(h) + return; + switch(type) { + case ObjectType::SYMBOL_PIN : + switch(property) { + case ObjectProperty::ID::LENGTH: + sym.pins.at(uu).length = std::max(value, (int64_t)0); + break; + default : + ; + } + break; + + default : + ; + } + rebuild(); + } + + void CoreSymbol::rebuild(bool from_undo) { + sym.expand(); + sym_work = sym; + Core::rebuild(from_undo); + } + + void CoreSymbol::history_push() { + history.push_back(std::make_unique(sym)); + } + + void CoreSymbol::history_load(unsigned int i) { + auto x = dynamic_cast(history.at(history_current).get()); + sym = x->sym; + rebuild(true); + } + + const Symbol *CoreSymbol::get_canvas_data() { + return &sym_work; + } + + std::pair CoreSymbol::get_bbox() { + auto bb = sym_work.get_bbox(true); + int64_t pad = 1_mm; + bb.first.x -= pad; + bb.first.y -= pad; + + bb.second.x += pad; + bb.second.y += pad; + return bb; + } + + Symbol *CoreSymbol::get_symbol(bool work) { + return work?&sym_work:&sym; + } + + void CoreSymbol::commit() { + sym = sym_work; + } + + void CoreSymbol::revert() { + sym_work = sym; + reverted = true; + } + + void CoreSymbol::save() { + s_signal_save.emit(); + + std::ofstream ofs(m_filename); + if(!ofs.is_open()) { + std::cout << "can't save symbol" < +#include +#include + +namespace horizon { + class CoreSymbol: public Core { + public: + CoreSymbol(const std::string &filename, Pool &pool); + bool has_object_type(ObjectType ty) override; + Symbol *get_symbol(bool work = true); + + Junction *get_junction(const UUID &uu, bool work=true); + Line *get_line(const UUID &uu, bool work=true); + SymbolPin *get_symbol_pin(const UUID &uu, bool work=true); + Arc *get_arc(const UUID &uu, bool work=true); + + Junction *insert_junction(const UUID &uu, bool work=true); + void delete_junction(const UUID &uu, bool work=true); + Line *insert_line(const UUID &uu, bool work=true); + void delete_line(const UUID &uu, bool work=true); + Arc *insert_arc(const UUID &uu, bool work=true); + void delete_arc(const UUID &uu, bool work=true); + + SymbolPin *insert_symbol_pin(const UUID &uu, bool work=true); + void delete_symbol_pin(const UUID &uu, bool work=true); + + + std::vector get_lines(bool work=true); + std::vector get_arcs(bool work=true); + std::vector get_pins(bool work=true); + + void rebuild(bool from_undo=false); + void commit(); + void revert(); + void save(); + + std::string get_property_string(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled=nullptr); + + bool get_property_bool(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled=nullptr); + void set_property_bool(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool value, bool *handled=nullptr); + + int64_t get_property_int(const UUID &uu, ObjectType type, ObjectProperty::ID property, bool *handled=nullptr); + void set_property_int(const UUID &uu, ObjectType type, ObjectProperty::ID property, int64_t value, bool *handled=nullptr); + + const Symbol *get_canvas_data(); + std::pair get_bbox() override; + + private: + std::map *get_text_map(bool work=true) override; + + + Symbol sym; + Symbol sym_work; + std::string m_filename; + + class HistoryItem: public Core::HistoryItem { + public: + HistoryItem(const Symbol &s):sym(s) {} + Symbol sym; + }; + void history_push(); + void history_load(unsigned int i); + }; +} diff --git a/core/cores.cpp b/core/cores.cpp new file mode 100644 index 000000000..565aca420 --- /dev/null +++ b/core/cores.cpp @@ -0,0 +1,17 @@ +#include "cores.hpp" +#include "core.hpp" +#include "core_schematic.hpp" +#include "core_symbol.hpp" +#include "core_padstack.hpp" +#include "core_package.hpp" +#include "core_board.hpp" + +namespace horizon { + Cores::Cores(Core *co): r(co), + c(dynamic_cast(r)), + y(dynamic_cast(r)), + a(dynamic_cast(r)), + k(dynamic_cast(r)), + b(dynamic_cast(r)) + {} +} diff --git a/core/cores.hpp b/core/cores.hpp new file mode 100644 index 000000000..c82630ee2 --- /dev/null +++ b/core/cores.hpp @@ -0,0 +1,16 @@ +#pragma once + + +namespace horizon { + class Cores { + public : + Cores(class Core *co); + Core *r; + class CoreSchematic *c; + class CoreSymbol *y; + class CorePadstack *a; + class CorePackage *k; + class CoreBoard *b; + }; + +} diff --git a/core/tool.cpp b/core/tool.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/core/tool_add_component.cpp b/core/tool_add_component.cpp new file mode 100644 index 000000000..2177bd74f --- /dev/null +++ b/core/tool_add_component.cpp @@ -0,0 +1,39 @@ +#include "tool_add_component.hpp" +#include +#include "core_schematic.hpp" + +namespace horizon { + + ToolAddComponent::ToolAddComponent(Core *c, ToolID tid): ToolBase(c, tid) { + name = "Add Component"; + } + + bool ToolAddComponent::can_begin() { + return core.c; + } + + ToolResponse ToolAddComponent::begin(const ToolArgs &args) { + std::cout << "tool add comp\n"; + bool r; + UUID entity_uuid; + std::tie(r, entity_uuid) = core.r->dialogs.select_entity(); + if(!r) { + return ToolResponse::end(); + } + Schematic *sch = core.c->get_schematic(); + auto uu = UUID::random(); + Component &comp = sch->block->components.emplace(uu, uu).first->second; + comp.entity = core.c->m_pool->get_entity(entity_uuid); + comp.refdes = comp.entity->prefix + "?"; + + core.c->selection.clear(); + core.c->selection.emplace(uu, ObjectType::COMPONENT); + core.c->commit(); + return ToolResponse::next(ToolID::MAP_SYMBOL); + } + ToolResponse ToolAddComponent::update(const ToolArgs &args) { + + return ToolResponse(); + } + +} diff --git a/core/tool_add_component.hpp b/core/tool_add_component.hpp new file mode 100644 index 000000000..75e4f6de2 --- /dev/null +++ b/core/tool_add_component.hpp @@ -0,0 +1,12 @@ +#pragma once +#include "core.hpp" + +namespace horizon { + class ToolAddComponent : public ToolBase { + public : + ToolAddComponent(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + }; +} diff --git a/core/tool_add_part.cpp b/core/tool_add_part.cpp new file mode 100644 index 000000000..72f2b0fa2 --- /dev/null +++ b/core/tool_add_part.cpp @@ -0,0 +1,42 @@ +#include "tool_add_part.hpp" +#include +#include "core_schematic.hpp" +#include "part.hpp" + +namespace horizon { + + ToolAddPart::ToolAddPart(Core *c, ToolID tid): ToolBase(c, tid) { + name = "Add Part"; + } + + bool ToolAddPart::can_begin() { + return core.c; + } + + ToolResponse ToolAddPart::begin(const ToolArgs &args) { + std::cout << "tool add part\n"; + bool r; + UUID part_uuid; + std::tie(r, part_uuid) = core.r->dialogs.select_part(UUID(), UUID()); + if(!r) { + return ToolResponse::end(); + } + auto part = core.c->m_pool->get_part(part_uuid); + Schematic *sch = core.c->get_schematic(); + auto uu = UUID::random(); + Component &comp = sch->block->components.emplace(uu, uu).first->second; + comp.entity = part->entity; + comp.part = part; + comp.refdes = comp.entity->prefix + "?"; + + core.c->selection.clear(); + core.c->selection.emplace(uu, ObjectType::COMPONENT); + core.c->commit(); + return ToolResponse::next(ToolID::MAP_SYMBOL); + } + ToolResponse ToolAddPart::update(const ToolArgs &args) { + + return ToolResponse(); + } + +} diff --git a/core/tool_add_part.hpp b/core/tool_add_part.hpp new file mode 100644 index 000000000..7fb8a585f --- /dev/null +++ b/core/tool_add_part.hpp @@ -0,0 +1,12 @@ +#pragma once +#include "core.hpp" + +namespace horizon { + class ToolAddPart : public ToolBase { + public : + ToolAddPart(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + }; +} diff --git a/core/tool_assign_part.cpp b/core/tool_assign_part.cpp new file mode 100644 index 000000000..ef28539c9 --- /dev/null +++ b/core/tool_assign_part.cpp @@ -0,0 +1,68 @@ +#include "tool_assign_part.hpp" +#include "core_schematic.hpp" +#include "part.hpp" +#include + +namespace horizon { + + ToolAssignPart::ToolAssignPart(Core *c, ToolID tid): ToolBase(c, tid) { + name = "Assign part"; + } + + bool ToolAssignPart::can_begin() { + return get_entity() != nullptr; + } + + Entity *ToolAssignPart::get_entity() { + Entity *entity = nullptr; + for(const auto &it : core.r->selection) { + if(it.type == ObjectType::SCHEMATIC_SYMBOL) { + auto sym = core.c->get_schematic_symbol(it.uuid); + if(entity) { + if(entity != sym->component->entity) { + return nullptr; + } + } + else { + entity = sym->component->entity; + comp = sym->component; + } + } + } + return entity; + } + + ToolResponse ToolAssignPart::begin(const ToolArgs &args) { + std::cout << "tool assing part\n"; + Entity *entity = get_entity(); + + if(!entity) { + return ToolResponse::end(); + } + UUID part_uuid = comp->part?comp->part->uuid:UUID(); + auto r = core.r->dialogs.select_part(entity->uuid, part_uuid); + if(r.first) { + Part *part = nullptr; + if(r.second) { + part = core.r->m_pool->get_part(r.second); + } + + for(const auto &it : args.selection) { + if(it.type == ObjectType::SCHEMATIC_SYMBOL) { + auto sym = core.c->get_schematic_symbol(it.uuid); + if(sym->component->entity == entity) { + sym->component->part = part; + } + } + } + + + core.r->commit(); + } + return ToolResponse::end(); + } + ToolResponse ToolAssignPart::update(const ToolArgs &args) { + return ToolResponse(); + } + +} diff --git a/core/tool_assign_part.hpp b/core/tool_assign_part.hpp new file mode 100644 index 000000000..9cbbd6be9 --- /dev/null +++ b/core/tool_assign_part.hpp @@ -0,0 +1,20 @@ +#pragma once +#include "core.hpp" +#include "component.hpp" +#include + +namespace horizon { + + class ToolAssignPart : public ToolBase { + public : + ToolAssignPart(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + + private: + class Entity *get_entity(); + Component *comp; + + }; +} diff --git a/core/tool_bend_line_net.cpp b/core/tool_bend_line_net.cpp new file mode 100644 index 000000000..ed0c676e5 --- /dev/null +++ b/core/tool_bend_line_net.cpp @@ -0,0 +1,64 @@ +#include "tool_bend_line_net.hpp" +#include +#include "core_schematic.hpp" + +namespace horizon { + + ToolBendLineNet::ToolBendLineNet(Core *c, ToolID tid): ToolBase(c, tid) { + name = "Bend net line"; + } + + bool ToolBendLineNet::can_begin() { + if(core.r->selection.size() != 1) + return false; + + const auto it = core.r->selection.begin(); + if(it->type != ObjectType::LINE_NET) + return false; + return true; + } + + ToolResponse ToolBendLineNet::begin(const ToolArgs &args) { + std::cout << "tool bend net line\n"; + + if(args.selection.size() != 1) + return ToolResponse::end(); + + const auto it = args.selection.begin(); + if(it->type != ObjectType::LINE_NET) + return ToolResponse::end(); + + temp = core.c->insert_junction(UUID::random()); + temp->temp = true; + temp->position = args.coords; + + LineNet *li = &core.c->get_sheet()->net_lines.at(it->uuid); + core.c->get_sheet()->split_line_net(li, temp); + + return ToolResponse(); + } + ToolResponse ToolBendLineNet::update(const ToolArgs &args) { + if(args.type == ToolEventType::MOVE) { + temp->position = args.coords; + } + else if(args.type == ToolEventType::CLICK) { + temp->temp = false; + if(args.button == 1) { + core.c->commit(); + return ToolResponse::end(); + } + else if(args.button == 3) { + core.c->revert(); + return ToolResponse::end(); + } + } + else if(args.type == ToolEventType::KEY) { + if(args.key == GDK_KEY_Escape) { + core.c->revert(); + return ToolResponse::end(); + } + } + return ToolResponse(); + } + +} diff --git a/core/tool_bend_line_net.hpp b/core/tool_bend_line_net.hpp new file mode 100644 index 000000000..389a3451f --- /dev/null +++ b/core/tool_bend_line_net.hpp @@ -0,0 +1,19 @@ +#pragma once +#include "core.hpp" +#include + +namespace horizon { + + class ToolBendLineNet : public ToolBase { + public : + ToolBendLineNet(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + + private: + Junction *temp = 0; + std::forward_list junctions_placed; + + }; +} diff --git a/core/tool_catalog.cpp b/core/tool_catalog.cpp new file mode 100644 index 000000000..68daa75d2 --- /dev/null +++ b/core/tool_catalog.cpp @@ -0,0 +1,45 @@ +#include "tool_catalog.hpp" + +namespace horizon { + const std::map tool_catalog = { + {ToolID::MOVE, {"Move"}}, + {ToolID::ROTATE, {"Rotate"}}, + {ToolID::MIRROR, {"Mirror"}}, + {ToolID::MOVE_EXACTLY, {"Move exactly"}}, + {ToolID::DELETE, {"Delete"}}, + {ToolID::ADD_COMPONENT, {"Place component"}}, + {ToolID::ADD_PART, {"Place part"}}, + {ToolID::BEND_LINE_NET, {"Bend net line"}}, + {ToolID::DISCONNECT, {"Disconnect symbol"}}, + {ToolID::DRAW_ARC, {"Draw arc"}}, + {ToolID::DRAW_LINE, {"Draw line"}}, + {ToolID::DRAW_NET, {"Draw net line"}}, + {ToolID::DRAW_POLYGON, {"Draw polyon"}}, + {ToolID::EDIT_COMPONENT_PIN_NAMES, {"Edit component pin names"}}, + {ToolID::ENTER_DATUM, {"Enter datum"}}, + {ToolID::MANAGE_BUSES, {"Manage buses"}}, + {ToolID::MAP_PIN, {"Map pin"}}, + {ToolID::MAP_SYMBOL, {"Place symbol"}}, + {ToolID::SELECT_NET_SEGMENT, {"Select net segment"}}, + {ToolID::MOVE_NET_SEGMENT, {"Move net segment to other net"}}, + {ToolID::MOVE_NET_SEGMENT_NEW, {"Move net segment to new net"}}, + {ToolID::PASTE, {"Paste"}}, + {ToolID::PLACE_BUS_LABEL, {"Place bus label"}}, + {ToolID::PLACE_BUS_RIPPER, {"Place bus ripper"}}, + {ToolID::PLACE_HOLE, {"Place hole"}}, + {ToolID::PLACE_JUNCTION, {"Place junction"}}, + {ToolID::PLACE_NET_LABEL, {"Place net label"}}, + {ToolID::PLACE_PAD, {"Place pad"}}, + {ToolID::PLACE_POWER_SYMBOL, {"Place power symbol"}}, + {ToolID::PLACE_TEXT, {"Place text"}}, + {ToolID::ASSIGN_PART, {"Assign part"}}, + {ToolID::MAP_PACKAGE, {"Place package"}}, + {ToolID::PLACE_VIA, {"Place via"}}, + {ToolID::ROUTE_TRACK, {"Route track"}}, + {ToolID::DRAG_KEEP_SLOPE, {"Drag and keep slope"}}, + {ToolID::DRAW_TRACK, {"Draw track"}}, + {ToolID::ANNOTATE, {"Annotate"}}, + {ToolID::SMASH, {"Smash"}}, + {ToolID::UNSMASH, {"Unsmash"}}, + }; +} diff --git a/core/tool_catalog.hpp b/core/tool_catalog.hpp new file mode 100644 index 000000000..b3c16d911 --- /dev/null +++ b/core/tool_catalog.hpp @@ -0,0 +1,14 @@ +#pragma once +#include "core.hpp" +#include + +namespace horizon { + class ToolCatalogItem { + public: + ToolCatalogItem(const std::string &n) :name(n) {}; + + const std::string name; + }; + + extern const std::map tool_catalog; +} diff --git a/core/tool_delete.cpp b/core/tool_delete.cpp new file mode 100644 index 000000000..18a88e52d --- /dev/null +++ b/core/tool_delete.cpp @@ -0,0 +1,296 @@ +#include "tool_delete.hpp" +#include "core_schematic.hpp" +#include "core_symbol.hpp" +#include "core_padstack.hpp" +#include "core_package.hpp" +#include "core_board.hpp" +#include + +namespace horizon { + + ToolDelete::ToolDelete(Core *c, ToolID tid) : ToolBase(c,tid) { + } + + bool ToolDelete::can_begin() { + return core.r->selection.size()>0; + + } + + ToolResponse ToolDelete::begin(const ToolArgs &args) { + std::cout << "tool delete\n"; + std::set delete_extra; + const auto lines = core.r->get_lines(); + const auto arcs = core.r->get_arcs(); + for(const auto &it : args.selection) { + if(it.type == ObjectType::JUNCTION) { + for(const auto line: lines) { + if(line->to.uuid == it.uuid || line->from.uuid == it.uuid) { + delete_extra.emplace(line->uuid, ObjectType::LINE); + } + } + if(core.c) { + for(const auto line: core.c->get_net_lines()) { + if(line->to.junc.uuid == it.uuid || line->from.junc.uuid == it.uuid) { + delete_extra.emplace(line->uuid, ObjectType::LINE_NET); + } + } + } + if(core.b) { + for(const auto track: core.b->get_tracks()) { + if(track->to.junc.uuid == it.uuid || track->from.junc.uuid == it.uuid) { + delete_extra.emplace(track->uuid, ObjectType::TRACK); + } + } + } + for(const auto arc: arcs) { + if(arc->to.uuid == it.uuid || arc->from.uuid == it.uuid || arc->center.uuid == it.uuid) { + delete_extra.emplace(arc->uuid, ObjectType::ARC); + } + } + if(core.c) { + for(const auto label: core.c->get_net_labels()) { + if(label->junction.uuid == it.uuid) { + delete_extra.emplace(label->uuid, ObjectType::NET_LABEL); + } + } + } + } + else if(it.type == ObjectType::SCHEMATIC_SYMBOL) { + core.c->get_schematic()->disconnect_symbol(core.c->get_sheet(), core.c->get_schematic_symbol(it.uuid)); + } + else if(it.type == ObjectType::BOARD_PACKAGE) { + core.b->get_board()->disconnect_package(&core.b->get_board()->packages.at(it.uuid)); + } + else if(it.type == ObjectType::POLYGON_EDGE) { + Polygon *poly = core.r->get_polygon(it.uuid); + auto vs = poly->get_vertices_for_edge(it.vertex); + delete_extra.emplace(poly->uuid, ObjectType::POLYGON_VERTEX, vs.first); + delete_extra.emplace(poly->uuid, ObjectType::POLYGON_VERTEX, vs.second); + } + } + delete_extra.insert(args.selection.begin(), args.selection.end()); + for(const auto &it: delete_extra) { + if(it.type == ObjectType::LINE_NET) { //need to erase net lines before symbols + LineNet *line = &core.c->get_sheet()->net_lines.at(it.uuid); + if(line->net) { + for(auto &it_ft: {line->from, line->to}) { + if(it_ft.is_pin()) { + UUIDPath<2> conn_path(it_ft.symbol->gate.uuid, it_ft.pin->uuid); + if(it_ft.symbol->component->connections.count(conn_path) && + it_ft.pin->connected_net_lines.size()<=1) { + it_ft.symbol->component->connections.erase(conn_path); + } + //prevents error in update_refs + it_ft.pin->connected_net_lines.erase(it.uuid); + } + } + } + auto from = line->from; + auto to = line->to; + Net *net = line->net; + core.c->get_sheet()->net_lines.erase(it.uuid); + core.c->get_sheet()->propagate_net_segments(); + if(net) { + UUID from_net_segment = from.get_net_segment(); + UUID to_net_segment = to.get_net_segment(); + if(from_net_segment != to_net_segment) { + auto pins_from = core.c->get_sheet()->get_pins_connected_to_net_segment(from_net_segment); + auto pins_to = core.c->get_sheet()->get_pins_connected_to_net_segment(to_net_segment); + std::cout << "!!!net split" << std::endl; + Block *b = core.c->get_schematic()->block; + if(!net->is_named() && !net->is_power && !net->is_bussed) { + //net is unnamed, not bussed and not a power net, user does't care which pins get extracted + b->extract_pins(pins_to); + } + else if(net->is_power) { + auto ns_info = core.c->get_sheet()->analyze_net_segments(); + if(ns_info.count(from_net_segment) && ns_info.count(to_net_segment)) { + auto &inf_from = ns_info.at(from_net_segment); + auto &inf_to = ns_info.at(to_net_segment); + if(inf_from.has_power_sym && inf_to.has_power_sym) { + //both have label, don't need to split net + } + else if(inf_from.has_power_sym && !inf_to.has_power_sym) { + //from has label, to not, extracts pins on to net segment + b->extract_pins(pins_to); + } + else if(!inf_from.has_power_sym && inf_to.has_power_sym) { + //to has label, from not, extract pins on from segment + b->extract_pins(pins_from); + } + else { + b->extract_pins(pins_from); + } + } + } + else { + auto ns_info = core.c->get_sheet()->analyze_net_segments(); + if(ns_info.count(from_net_segment) && ns_info.count(to_net_segment)) { + auto &inf_from = ns_info.at(from_net_segment); + auto &inf_to = ns_info.at(to_net_segment); + if(inf_from.has_label && inf_to.has_label) { + //both have label, don't need to split net + } + else if(inf_from.has_label && !inf_to.has_label) { + //from has label, to not, extracts pins on to net segment + b->extract_pins(pins_to); + } + else if(!inf_from.has_label && inf_to.has_label) { + //to has label, from not, extract pins on from segment + b->extract_pins(pins_from); + } + else if(!inf_from.has_label && !inf_to.has_label) { + //both segments are unlabeled, so don't care + b->extract_pins(pins_from); + } + } + } + } + } + } + else if(it.type == ObjectType::POWER_SYMBOL) { //need to erase power symbols before junctions + auto *power_sym = &core.c->get_sheet()->power_symbols.at(it.uuid); + auto sheet = core.c->get_sheet(); + Junction *j = power_sym->junction; + sheet->power_symbols.erase(power_sym->uuid); + + //now, we've got a net segment with one less power symbol + //let's see if there's still a power symbol + //if no, extract pins on this net segment to a new net + sheet->propagate_net_segments(); + auto ns = sheet->analyze_net_segments(); + auto &nsinfo = ns.at(j->net_segment); + if(!nsinfo.has_power_sym) { + auto pins = sheet->get_pins_connected_to_net_segment(j->net_segment); + core.c->get_schematic()->block->extract_pins(pins); + } + } + else if(it.type == ObjectType::BUS_RIPPER) { //need to erase bus rippers junctions + auto *ripper = &core.c->get_sheet()->bus_rippers.at(it.uuid); + auto sheet = core.c->get_sheet(); + if(ripper->connection_count == 0) { + //just delete it + sheet->bus_rippers.erase(ripper->uuid); + } + else { + Junction *j = sheet->replace_bus_ripper(ripper); + sheet->bus_rippers.erase(ripper->uuid); + sheet->propagate_net_segments(); + auto ns = sheet->analyze_net_segments(); + auto &nsinfo = ns.at(j->net_segment); + if(!nsinfo.has_label) { + auto pins = sheet->get_pins_connected_to_net_segment(j->net_segment); + core.c->get_schematic()->block->extract_pins(pins); + } + + } + + /*Junction *j = power_sym->junction; + sheet->bus_rippers.erase(power_sym->uuid); + + //now, we've got a net segment with one less power symbol + //let's see if there's still a power symbol + //if no, extract pins on this net segment to a new net + sheet->propagate_net_segments(); + auto ns = sheet->analyze_net_segments(); + auto &nsinfo = ns.at(j->net_segment); + if(!nsinfo.has_power_sym) { + auto pins = sheet->get_pins_connected_to_net_segment(j->net_segment); + core.c->get_schematic()->block->extract_pins(pins); + }*/ + } + + } + + std::set polys_del; + for(const auto &it: delete_extra) { + switch(it.type) { + case ObjectType::LINE : + core.r->delete_line(it.uuid); + break; + case ObjectType::TRACK : + core.b->get_board()->tracks.erase(it.uuid); + break; + case ObjectType::JUNCTION : + core.r->delete_junction(it.uuid); + break; + case ObjectType::HOLE : + core.r->delete_hole(it.uuid); + break; + case ObjectType::PAD : + core.k->get_package()->pads.erase(it.uuid); + break; + case ObjectType::ARC : + core.r->delete_arc(it.uuid); + break; + case ObjectType::SYMBOL_PIN : + core.y->delete_symbol_pin(it.uuid); + break; + case ObjectType::BOARD_PACKAGE: + core.b->get_board()->packages.erase(it.uuid); + break; + case ObjectType::SCHEMATIC_SYMBOL: { + SchematicSymbol *schsym = core.c->get_schematic_symbol(it.uuid); + Component *comp = schsym->component.ptr; + core.c->delete_schematic_symbol(it.uuid); + Schematic *sch = core.c->get_schematic(); + bool found = false; + for(auto &it_sheet:sch->sheets) { + for(auto &it_sym: it_sheet.second.symbols) { + if(it_sym.second.component.uuid == comp->uuid) { + found = true; + break; + } + } + if(found) { + break; + } + } + if(found == false) { + if((comp->entity->gates.size()==1) || core.r->dialogs.ask_delete_component(comp)) { + sch->block->components.erase(comp->uuid); + comp = nullptr; + } + } + }break; + case ObjectType::NET_LABEL : { + core.c->get_sheet()->net_labels.erase(it.uuid); + }break; + case ObjectType::BUS_LABEL : { + core.c->get_sheet()->bus_labels.erase(it.uuid); + }break; + case ObjectType::VIA : + core.b->get_board()->vias.erase(it.uuid); + break; + case ObjectType::TEXT : + core.r->delete_text(it.uuid); + break; + case ObjectType::POLYGON_VERTEX : { + Polygon *poly = core.r->get_polygon(it.uuid); + poly->vertices.at(it.vertex).remove = true; + polys_del.insert(poly); + } break; + case ObjectType::POLYGON_ARC_CENTER : { + Polygon *poly = core.r->get_polygon(it.uuid); + poly->vertices.at(it.vertex).type = Polygon::Vertex::Type::LINE; + } break; + + case ObjectType::INVALID : + break; + } + } + for(auto it: polys_del) { + it->vertices.erase(std::remove_if(it->vertices.begin(), it->vertices.end(), [](const auto &x){return x.remove;}), it->vertices.end()); + if(!it->is_valid()) { + core.r->delete_polygon(it->uuid); + } + } + + core.r->commit(); + return ToolResponse::end(); + } + ToolResponse ToolDelete::update(const ToolArgs &args) { + return ToolResponse(); + } + +} diff --git a/core/tool_delete.hpp b/core/tool_delete.hpp new file mode 100644 index 000000000..88f320100 --- /dev/null +++ b/core/tool_delete.hpp @@ -0,0 +1,16 @@ +#pragma once +#include "core.hpp" + +namespace horizon { + + class ToolDelete : public ToolBase { + public : + ToolDelete(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + + private: + + }; +} diff --git a/core/tool_disconnect.cpp b/core/tool_disconnect.cpp new file mode 100644 index 000000000..c1f09cce1 --- /dev/null +++ b/core/tool_disconnect.cpp @@ -0,0 +1,33 @@ +#include "tool_disconnect.hpp" +#include +#include "core_schematic.hpp" + +namespace horizon { + + ToolDisconnect::ToolDisconnect(Core *c, ToolID tid): ToolBase(c, tid) { + } + + bool ToolDisconnect::can_begin() { + for(const auto &it : core.r->selection) { + if(it.type == ObjectType::SCHEMATIC_SYMBOL) { + return true; + } + } + return false; + } + + ToolResponse ToolDisconnect::begin(const ToolArgs &args) { + std::cout << "tool disconnect\n"; + for(const auto &it : args.selection) { + if(it.type == ObjectType::SCHEMATIC_SYMBOL) { + core.c->get_schematic()->disconnect_symbol(core.c->get_sheet(), core.c->get_schematic_symbol(it.uuid)); + } + } + core.c->commit(); + return ToolResponse::end(); + } + ToolResponse ToolDisconnect::update(const ToolArgs &args) { + return ToolResponse(); + } + +} diff --git a/core/tool_disconnect.hpp b/core/tool_disconnect.hpp new file mode 100644 index 000000000..e9d8ec51f --- /dev/null +++ b/core/tool_disconnect.hpp @@ -0,0 +1,17 @@ +#pragma once +#include "core.hpp" +#include + +namespace horizon { + + class ToolDisconnect : public ToolBase { + public : + ToolDisconnect(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + + private: + + }; +} diff --git a/core/tool_drag_keep_slope.cpp b/core/tool_drag_keep_slope.cpp new file mode 100644 index 000000000..2e695c4b3 --- /dev/null +++ b/core/tool_drag_keep_slope.cpp @@ -0,0 +1,129 @@ +#include "tool_drag_keep_slope.hpp" +#include +#include "core_board.hpp" + +namespace horizon { + + ToolDragKeepSlope::ToolDragKeepSlope(Core *c, ToolID tid): ToolBase(c, tid) { + name = "Drag keeping slope"; + } + + bool ToolDragKeepSlope::can_begin() { + if(!core.b) + return false; + for(const auto &it: core.r->selection) { + if(it.type == ObjectType::TRACK) + return true; + } + return false; + } + + ToolDragKeepSlope::TrackInfo::TrackInfo(Track *tr, Track *fr, Track *to): track(tr), track_from(fr), track_to(to){ + if(track_from->from.junc == track->from.junc) { + pos_from2 = track_from->to.get_position(); + } + else { + pos_from2 = track_from->from.get_position(); + } + if(track_to->from.junc == track->to.junc) { + pos_to2 = track_to->to.get_position(); + } + else { + pos_to2 = track_to->from.get_position(); + } + pos_from_orig = tr->from.get_position(); + pos_to_orig = tr->to.get_position(); + } + + ToolResponse ToolDragKeepSlope::begin(const ToolArgs &args) { + std::cout << "tool drag keep slope\n"; + + + core.r->selection.clear(); + for(const auto &it: args.selection) { + if(it.type == ObjectType::TRACK) { + auto track = &core.b->get_board()->tracks.at(it.uuid); + if(track->from.is_junc() && track->to.is_junc()) { + if(track->from.junc->connection_count == 2 && track->to.junc->connection_count == 2) { + Track *tr_from = nullptr; + Track *tr_to = nullptr; + for(auto &it_tr: core.b->get_board()->tracks) { + auto track_other = &it_tr.second; + if(track_other == track) + continue; + for(const auto &it_ft: {track_other->from, track_other->to}) { + if(it_ft.junc == track->from.junc) { + assert(tr_from==nullptr); + tr_from = track_other; + } + if(it_ft.junc == track->to.junc) { + assert(tr_to==nullptr); + tr_to = track_other; + } + } + } + assert(tr_from); + assert(tr_to); + if((tr_from->from.get_position() != tr_from->to.get_position()) && (tr_to->from.get_position() != tr_to->to.get_position())) { + track_info.emplace_back(track, tr_from, tr_to); + core.r->selection.emplace(it); + core.r->selection.emplace(track->from.junc->uuid, ObjectType::JUNCTION); + core.r->selection.emplace(track->to.junc->uuid, ObjectType::JUNCTION); + } + } + } + } + } + pos_orig = args.coords; + + if(core.r->selection.size() < 1) + return ToolResponse::end(); + + return ToolResponse(); + } + + typedef Coord Coordd; + + ToolResponse ToolDragKeepSlope::update(const ToolArgs &args) { + if(args.type == ToolEventType::MOVE) { + for(const auto &it: track_info) { + Coordd vfrom = it.pos_from_orig-it.pos_from2; + Coordd vto = it.pos_to_orig-it.pos_to2; + Coordd vtr = it.pos_to_orig - it.pos_from_orig; + Coordd vtrn(vtr.y, -vtr.x); + + Coordd vshift = args.coords - pos_orig; + Coordd vshift2 = (vtrn*(vtrn.dot(vshift)))/vtrn.mag_sq(); + + Coordd shift_from = (vfrom*vshift2.mag_sq())/(vfrom.dot(vshift2)); + Coordd shift_to = (vto*vshift2.mag_sq())/(vto.dot(vshift2)); + if(vshift2.mag_sq()==0) { + shift_from = {0,0}; + shift_to = {0,0}; + } + + it.track->from.junc->position = it.pos_from_orig + Coordi(shift_from.x, shift_from.y); + it.track->to.junc->position = it.pos_to_orig + Coordi(shift_to.x, shift_to.y); + } + } + else if(args.type == ToolEventType::CLICK) { + if(args.button == 1) { + core.b->commit(); + return ToolResponse::end(); + } + else if(args.button == 3) { + core.b->revert(); + return ToolResponse::end(); + } + } + + else if(args.type == ToolEventType::KEY) { + if(args.key == GDK_KEY_Escape) { + core.b->revert(); + return ToolResponse::end(); + } + } + return ToolResponse(); + } + +} diff --git a/core/tool_drag_keep_slope.hpp b/core/tool_drag_keep_slope.hpp new file mode 100644 index 000000000..852bc7237 --- /dev/null +++ b/core/tool_drag_keep_slope.hpp @@ -0,0 +1,33 @@ +#pragma once +#include "core.hpp" +#include "track.hpp" +#include + +namespace horizon { + + class ToolDragKeepSlope: public ToolBase { + public : + ToolDragKeepSlope(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + + private: + class TrackInfo { + public: + Track *track = nullptr; //the track itself + Track *track_from = nullptr; + Track *track_to = nullptr; + Coordi pos_from2; + Coordi pos_to2; + Coordi pos_from_orig; + Coordi pos_to_orig; + TrackInfo(Track *tr, Track *fr, Track *to); + //TrackInfo(Track *a, Track *b, Track *c): track(a), track_from(b), track_to(c) {} + }; + + std::deque track_info; + Coordi pos_orig; + + }; +} diff --git a/core/tool_draw_arc.cpp b/core/tool_draw_arc.cpp new file mode 100644 index 000000000..6fd76a4f0 --- /dev/null +++ b/core/tool_draw_arc.cpp @@ -0,0 +1,98 @@ +#include "tool_draw_arc.hpp" +#include + +namespace horizon { + + ToolDrawArc::ToolDrawArc(Core *c, ToolID tid):ToolBase(c, tid) { + name = "Draw Arc"; + } + + bool ToolDrawArc::can_begin() { + return core.r->has_object_type(ObjectType::ARC); + } + + Junction *ToolDrawArc::make_junction(const Coordi &coords) { + Junction *ju; + ju = core.r->insert_junction(UUID::random()); + ju->temp = true; + ju->position = coords; + return ju; + } + + ToolResponse ToolDrawArc::begin(const ToolArgs &args) { + std::cout << "tool draw arc\n"; + + temp_junc = make_junction(args.coords); + temp_arc = nullptr; + from_junc = nullptr; + to_junc = nullptr; + state = DrawArcState::FROM; + + return ToolResponse(); + } + ToolResponse ToolDrawArc::update(const ToolArgs &args) { + if(args.type == ToolEventType::MOVE) { + temp_junc->position = args.coords; + } + else if(args.type == ToolEventType::CLICK) { + if(args.button == 1) { + if(state == DrawArcState::FROM) { + if(args.target.type == ObjectType::JUNCTION) { + from_junc = core.r->get_junction(args.target.path.at(0)); + } + else { + temp_junc->temp = false; + from_junc = temp_junc; + temp_junc = make_junction(args.coords); + } + state = DrawArcState::TO; + } + else if(state == DrawArcState::TO) { + if(args.target.type == ObjectType::JUNCTION) { + to_junc = core.r->get_junction(args.target.path.at(0)); + } + else { + temp_junc->temp = false; + to_junc = temp_junc; + temp_junc = make_junction(args.coords); + } + temp_arc = core.r->insert_arc(UUID::random()); + temp_arc->from = from_junc; + temp_arc->to = to_junc; + temp_arc->center = temp_junc; + state = DrawArcState::CENTER; + } + else if(state == DrawArcState::CENTER) { + if(args.target.type == ObjectType::JUNCTION) { + temp_arc->center = core.r->get_junction(args.target.path.at(0)); + core.r->delete_junction(temp_junc->uuid); + temp_junc = nullptr; + } + else { + temp_junc->temp = false; + temp_arc->center = temp_junc; + } + core.r->commit(); + return ToolResponse::end(); + } + } + else if(args.button == 3) { + core.r->revert(); + return ToolResponse::end(); + } + } + else if(args.type == ToolEventType::KEY) { + if(args.key == GDK_KEY_Escape) { + core.r->revert(); + return ToolResponse::end(); + } + else if(args.key == GDK_KEY_e) { + if(temp_arc) { + temp_arc->reverse(); + } + } + } + return ToolResponse(); + } + +} diff --git a/core/tool_draw_arc.hpp b/core/tool_draw_arc.hpp new file mode 100644 index 000000000..ec7931ca9 --- /dev/null +++ b/core/tool_draw_arc.hpp @@ -0,0 +1,23 @@ +#pragma once +#include "core.hpp" + +namespace horizon { + + class ToolDrawArc : public ToolBase { + public : + ToolDrawArc(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + + private: + enum class DrawArcState {FROM, TO, CENTER}; + DrawArcState state; + Junction *temp_junc = 0; + Junction *from_junc = 0; + Junction *to_junc = 0; + Arc *temp_arc = 0; + Junction *make_junction(const Coordi &coords); + + }; +} diff --git a/core/tool_draw_line.cpp b/core/tool_draw_line.cpp new file mode 100644 index 000000000..cae2dfe86 --- /dev/null +++ b/core/tool_draw_line.cpp @@ -0,0 +1,74 @@ +#include "tool_draw_line.hpp" +#include "core_schematic.hpp" +#include "core_symbol.hpp" +#include + +namespace horizon { + + ToolDrawLine::ToolDrawLine(Core *c, ToolID tid):ToolBase(c, tid) { + name = "Draw Line"; + } + + bool ToolDrawLine::can_begin() { + return core.r->has_object_type(ObjectType::LINE); + } + + + ToolResponse ToolDrawLine::begin(const ToolArgs &args) { + std::cout << "tool draw line junction\n"; + + temp_junc = core.r->insert_junction(UUID::random()); + temp_junc->temp = true; + temp_junc->position = args.coords; + temp_line = nullptr; + + return ToolResponse(); + } + ToolResponse ToolDrawLine::update(const ToolArgs &args) { + if(args.type == ToolEventType::MOVE) { + temp_junc->position = args.coords; + } + else if(args.type == ToolEventType::CLICK) { + if(args.button == 1) { + if(args.target.type == ObjectType::JUNCTION) { + if(temp_line != nullptr) { + temp_line->to = core.r->get_junction(args.target.path.at(0)); + } + temp_line = core.r->insert_line(UUID::random()); + temp_line->from = core.r->get_junction(args.target.path.at(0)); + temp_line->to = temp_junc; + } + else { + Junction *last = temp_junc; + temp_junc->temp = false; + temp_junc = core.r->insert_junction(UUID::random()); + temp_junc->temp = true; + temp_junc->position = args.coords; + + temp_line = core.r->insert_line(UUID::random()); + temp_line->layer = args.work_layer; + temp_line->from = last; + temp_line->to = temp_junc; + } + } + else if(args.button == 3) { + if(temp_line) { + core.r->delete_line(temp_line->uuid); + temp_line = nullptr; + } + core.r->delete_junction(temp_junc->uuid); + temp_junc = nullptr; + core.r->commit(); + return ToolResponse::end(); + } + } + else if(args.type == ToolEventType::KEY) { + if(args.key == GDK_KEY_Escape) { + core.r->revert(); + return ToolResponse::end(); + } + } + return ToolResponse(); + } + +} diff --git a/core/tool_draw_line.hpp b/core/tool_draw_line.hpp new file mode 100644 index 000000000..094f03698 --- /dev/null +++ b/core/tool_draw_line.hpp @@ -0,0 +1,18 @@ +#pragma once +#include "core.hpp" + +namespace horizon { + + class ToolDrawLine : public ToolBase { + public : + ToolDrawLine(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + + private: + Junction *temp_junc = 0; + Line *temp_line = 0; + + }; +} diff --git a/core/tool_draw_line_net.cpp b/core/tool_draw_line_net.cpp new file mode 100644 index 000000000..a6e71eb9a --- /dev/null +++ b/core/tool_draw_line_net.cpp @@ -0,0 +1,351 @@ +#include "tool_draw_line_net.hpp" +#include +#include "core_schematic.hpp" + +namespace horizon { + + ToolDrawLineNet::ToolDrawLineNet(Core *c, ToolID tid):ToolBase(c, tid) { + name = "Draw Net Line"; + } + + bool ToolDrawLineNet::can_begin() { + return core.c; + } + + + ToolResponse ToolDrawLineNet::begin(const ToolArgs &args) { + std::cout << "tool draw net line junction\n"; + + temp_junc = core.c->insert_junction(UUID::random()); + temp_junc->temp = true; + temp_junc->position = args.coords; + temp_line = nullptr; + core.c->selection.clear(); + + return ToolResponse(); + } + + void ToolDrawLineNet::move_temp_junc(const Coordi &c) { + if(!temp_line) { + temp_junc->position = c; + } + else { + switch(restrict_mode) { + case Restrict::NONE : + temp_junc->position = c; + break; + + case Restrict::X : + temp_junc->position = {c.x, temp_line->from.get_position().y}; + break; + case Restrict::Y : + temp_junc->position = {temp_line->from.get_position().x, c.y}; + break; + } + } + } + + int ToolDrawLineNet::merge_nets(Net *net, Net *into) { + //fixme: mergeing bussed power nets :| + if(net->is_bussed || into->is_bussed) { + if(net->is_bussed && into->is_bussed) { + return 1; //can't merge bussed nets + } + else if(!net->is_bussed && into->is_bussed) { + //merging non-bussed net into bussed net is fine + } + else if(net->is_bussed && !into->is_bussed) { + std::swap(net, into); + } + } + else if(net->is_power || into->is_power) { + if(net->is_power && into->is_power) { + return 1; + } + else if(!net->is_power && into->is_power) { + //merging non-power net into power net is fine + } + else if(net->is_power && !into->is_power) { + std::swap(net, into); + } + } + else if(net->is_named() && into->is_named()) { + int r = core.r->dialogs.ask_net_merge(net, into); + if(r==1) { + //nop, nets are already as we want them to + } + else if(r == 2) { + std::swap(net, into); + } + else { + //don't merge nets + return 1; + } + } + else if(net->is_named()) { + std::swap(net, into); + } + core.c->get_schematic()->block->merge_nets(net, into); //net will be erased + core.c->get_schematic()->expand(true); //be careful + + std::cout << "merging nets" << std::endl; + return 0; + } + + ToolResponse ToolDrawLineNet::end(bool delete_junction) { + temp_junc->temp = false; + if(delete_junction) + core.c->delete_junction(temp_junc->uuid); + core.c->commit(); + return ToolResponse::end(); + } + + ToolResponse ToolDrawLineNet::update(const ToolArgs &args) { + if(args.type == ToolEventType::MOVE) { + move_temp_junc(args.coords); + } + else if(args.type == ToolEventType::CLICK) { + if(args.button == 1) { + if(args.target.type == ObjectType::JUNCTION) { + uuid_ptr j = core.c->get_junction(args.target.path.at(0)); + if(temp_line != nullptr) { + UUID tlu = temp_line->uuid; + if(temp_line->bus || j->bus) { //either is bus + if(temp_line->net || j->net) + return ToolResponse(); //bus-net illegal + + if(temp_line->bus && j->bus) { //both are bus + if(temp_line->bus != j->bus) //not the same bus + return ToolResponse(); //illegal + } + else if(temp_line->bus && !j->bus) { + j->bus = temp_line->bus; + } + else if(!temp_line->bus && j->bus) { + temp_line->bus = j->bus; + } + } + if(temp_line->net && j->net && j->net.uuid != temp_line->net->uuid) { + if(merge_nets(j->net, temp_line->net)) { + return ToolResponse(); + } + } + if(j->net) { + temp_line->net = j->net; + } + else { + j->net = temp_line->net; + } + temp_line->to.connect(j); + Sheet *sh = core.c->get_sheet(); + assert(sh->junctions.count(j.uuid)); + assert(sh->net_lines.count(tlu)); + + return end(); + + } + + temp_line = core.c->insert_line_net(UUID::random()); + temp_junc->net = j->net; + temp_junc->bus = j->bus; + temp_line->from.connect(j); + temp_line->to.connect(temp_junc); + temp_line->net = j->net; + temp_line->bus = j->bus; + + } + else if(args.target.type == ObjectType::SYMBOL_PIN) { + SchematicSymbol *sym = core.c->get_schematic_symbol(args.target.path.at(0)); + UUIDPath<2> connpath(sym->gate->uuid, args.target.path.at(1)); + + if(sym->component->connections.count(connpath)) { + Connection &conn = sym->component->connections.at(connpath); + if(temp_line != nullptr) { + if(temp_line->bus) { + return ToolResponse(); + } + if(temp_line->net && (temp_line->net->uuid != conn.net->uuid)) { + if(merge_nets(conn.net, temp_line->net)) { + return ToolResponse(); + } + } + temp_line->to.connect(sym, &sym->pool_symbol->pins.at(args.target.path.at(1))); + return end(); + } + else { + temp_line = core.c->insert_line_net(UUID::random()); + temp_line->from.connect(sym, &sym->pool_symbol->pins.at(args.target.path.at(1))); + temp_line->to.connect(temp_junc); + temp_junc->net = conn.net; + temp_line->net = conn.net; + } + } + else { + Net *net = nullptr; + bool have_line = false; + if(temp_line != nullptr) { //already have line + if(temp_line->bus) { + return ToolResponse(); + } + if(!temp_line->net) { //temp line has no net, create one + net = core.c->get_schematic()->block->insert_net(); + temp_line->net = net; + } + else { //temp line has net + net = temp_line->net; + } + sym->component->connections.emplace(connpath, temp_line->net); + //terminate line + temp_line->to.connect(sym, &sym->pool_symbol->pins.at(args.target.path.at(1))); + have_line = true; + } + //create new line and net + if(!net) { + net = core.c->get_schematic()->block->insert_net(); + sym->component->connections.emplace(connpath, net); + } + if(have_line) { + return end(); + } + + temp_line = core.c->insert_line_net(UUID::random()); + temp_line->from.connect(sym, &sym->pool_symbol->pins.at(args.target.path.at(1))); + temp_line->to.connect(temp_junc); + temp_junc->net = net; + temp_line->net = net; + + } + + } + else if(args.target.type == ObjectType::BUS_RIPPER) { + BusRipper *ripper = &core.c->get_sheet()->bus_rippers.at(args.target.path.at(0)); + if(temp_line != nullptr) { + if(temp_line->bus) { + return ToolResponse(); + } + if(temp_line->net && (temp_line->net->uuid != ripper->bus_member->net->uuid)) { + if(merge_nets(ripper->bus_member->net, temp_line->net)) { + return ToolResponse(); + } + } + temp_line->to.connect(ripper); + return end(); + } + temp_line = core.c->insert_line_net(UUID::random()); + temp_line->from.connect(ripper); + temp_line->to.connect(temp_junc); + temp_junc->net = ripper->bus_member->net; + temp_line->net = ripper->bus_member->net; + } + + else { + Junction *last = temp_junc; + + + for(auto it: core.c->get_net_lines()) { + if(it->coord_on_line(temp_junc->position)) { + if(it != temp_line) { + bool is_temp_line = false; + for(const auto &it_ft: {it->from, it->to}) { + if(it_ft.is_junc()) { + if(it_ft.junc->temp) + is_temp_line = true; + } + } + if(!is_temp_line) { + std::cout << "on line" << (std::string)it->uuid << std::endl; + if(temp_junc->bus || it->bus) { //either is bus + if(temp_junc->net || it->net) + return ToolResponse(); //bus-net illegal + + if(temp_junc->bus && it->bus) { //both are bus + if(temp_junc->bus != it->bus) //not the same bus + return ToolResponse(); //illegal + } + else if(temp_junc->bus && !it->bus) { + it->bus = temp_junc->bus; + } + else if(!temp_junc->bus && it->bus) { + temp_junc->bus = it->bus; + } + } + if(temp_junc->net && it->net && it->net.uuid != temp_junc->net->uuid) { + if(merge_nets(it->net, temp_junc->net)) { + return ToolResponse(); + } + } + auto li = core.c->get_sheet()->split_line_net(it, temp_junc); + temp_junc->net = li->net; + temp_junc->bus = li->bus; + + if(temp_line) + return end(false); + + break; + } + } + } + } + temp_junc = core.c->insert_junction(UUID::random()); + temp_junc->temp = true; + temp_junc->position = args.coords; + if(last && temp_line) { + temp_line->net = last->net; + temp_line->bus = last->bus; + } + if(temp_line) { + temp_junc->net = temp_line->net; + temp_junc->bus = temp_line->bus; + } + if(last) { + temp_junc->net = last->net; + temp_junc->bus = last->bus; + } + + temp_line = core.c->insert_line_net(UUID::random()); + temp_line->from.connect(last); + temp_line->to.connect(temp_junc); + temp_line->net = last->net; + temp_line->bus = last->bus; + + if(restrict_mode == Restrict::X) { + restrict_mode = Restrict::Y; + } + else if(restrict_mode == Restrict::Y) { + restrict_mode = Restrict::X; + } + } + } + else if(args.button == 3) { + if(temp_line) { + core.c->delete_line_net(temp_line->uuid); + temp_line = nullptr; + } + core.c->delete_junction(temp_junc->uuid); + temp_junc = nullptr; + core.c->commit(); + return ToolResponse::end(); + } + } + else if(args.type == ToolEventType::KEY) { + if(args.key == GDK_KEY_Escape) { + core.c->revert(); + return ToolResponse::end(); + } + if(args.key == GDK_KEY_slash) { + if(restrict_mode == Restrict::NONE) { + restrict_mode = Restrict::X; + } + else if(restrict_mode == Restrict::X) { + restrict_mode = Restrict::Y; + } + else if(restrict_mode == Restrict::Y) { + restrict_mode = Restrict::NONE; + } + move_temp_junc(args.coords); + } + } + return ToolResponse(); + } + +} diff --git a/core/tool_draw_line_net.hpp b/core/tool_draw_line_net.hpp new file mode 100644 index 000000000..18e56c3b8 --- /dev/null +++ b/core/tool_draw_line_net.hpp @@ -0,0 +1,24 @@ +#pragma once +#include "core.hpp" + +namespace horizon { + + class ToolDrawLineNet : public ToolBase { + public : + ToolDrawLineNet(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + + private: + Junction *temp_junc = 0; + Junction *temp_junc_2 = 0; + LineNet *temp_line = 0; + LineNet *temp_line_2 = 0; + enum class Restrict{NONE, X, Y}; + Restrict restrict_mode=Restrict::NONE; + void move_temp_junc(const Coordi &c); + int merge_nets(Net *net, Net *into); + ToolResponse end(bool delete_junction=true); + }; +} diff --git a/core/tool_draw_polygon.cpp b/core/tool_draw_polygon.cpp new file mode 100644 index 000000000..e25f95b87 --- /dev/null +++ b/core/tool_draw_polygon.cpp @@ -0,0 +1,82 @@ +#include "tool_draw_polygon.hpp" +#include "core_padstack.hpp" +#include + +namespace horizon { + + ToolDrawPolygon::ToolDrawPolygon(Core *c, ToolID tid):ToolBase(c, tid) { + name = "Draw Polygon"; + } + + bool ToolDrawPolygon::can_begin() { + return core.r->has_object_type(ObjectType::POLYGON); + } + + + ToolResponse ToolDrawPolygon::begin(const ToolArgs &args) { + std::cout << "tool draw line poly\n"; + + + temp = core.r->insert_polygon(UUID::random()); + temp->temp = true; + temp->layer = args.work_layer; + vertex = temp->append_vertex(); + vertex->position = args.coords; + + + return ToolResponse(); + } + ToolResponse ToolDrawPolygon::update(const ToolArgs &args) { + if(args.type == ToolEventType::MOVE) { + if(arc_mode && last_vertex) { + last_vertex->arc_center = args.coords; + } + else { + vertex->position = args.coords; + } + } + else if(args.type == ToolEventType::CLICK) { + if(args.button == 1) { + if(arc_mode) { + arc_mode = false; + vertex->position = args.coords; + } + else { + last_vertex = vertex; + vertex = temp->append_vertex(); + vertex->position = args.coords; + } + } + else if(args.button == 3) { + temp->vertices.pop_back(); + temp->temp = false; + vertex = nullptr; + if(!temp->is_valid()) { + core.r->delete_polygon(temp->uuid); + } + core.r->commit(); + return ToolResponse::end(); + } + } + else if(args.type == ToolEventType::KEY) { + if(args.key == GDK_KEY_a) { + if(last_vertex) { + last_vertex->type = Polygon::Vertex::Type::ARC; + last_vertex->arc_center = args.coords; + arc_mode = true; + + } + }if(args.key == GDK_KEY_e) { + if(last_vertex && (last_vertex->type == Polygon::Vertex::Type::ARC)) { + last_vertex->arc_reverse ^= 1; + } + } + else if(args.key == GDK_KEY_Escape) { + core.r->revert(); + return ToolResponse::end(); + } + } + return ToolResponse(); + } + +} diff --git a/core/tool_draw_polygon.hpp b/core/tool_draw_polygon.hpp new file mode 100644 index 000000000..f131c3a2e --- /dev/null +++ b/core/tool_draw_polygon.hpp @@ -0,0 +1,21 @@ +#pragma once +#include "core.hpp" +#include "polygon.hpp" + +namespace horizon { + + class ToolDrawPolygon : public ToolBase { + public : + ToolDrawPolygon(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + + private: + Polygon *temp = nullptr; + Polygon::Vertex *vertex = nullptr; + Polygon::Vertex *last_vertex = nullptr; + bool arc_mode = false; + + }; +} diff --git a/core/tool_draw_track.cpp b/core/tool_draw_track.cpp new file mode 100644 index 000000000..123910b10 --- /dev/null +++ b/core/tool_draw_track.cpp @@ -0,0 +1,176 @@ +#include "tool_draw_track.hpp" +#include +#include "core_board.hpp" + +namespace horizon { + + ToolDrawTrack::ToolDrawTrack(Core *c, ToolID tid):ToolBase(c, tid) { + name = "Draw Track"; + } + + bool ToolDrawTrack::can_begin() { + return core.b; + } + + + ToolResponse ToolDrawTrack::begin(const ToolArgs &args) { + std::cout << "tool draw track\n"; + + temp_junc = core.b->insert_junction(UUID::random()); + temp_junc->temp = true; + temp_junc->position = args.coords; + temp_track = nullptr; + core.b->selection.clear(); + + return ToolResponse(); + } + + Track *ToolDrawTrack::create_temp_track() { + auto uu = UUID::random(); + temp_track = &core.b->get_board()->tracks.emplace(uu, uu).first->second; + return temp_track; + } + + ToolResponse ToolDrawTrack::update(const ToolArgs &args) { + if(args.type == ToolEventType::MOVE) { + temp_junc->position = args.coords; + core.b->get_board()->update_airwires(); + } + else if(args.type == ToolEventType::CLICK) { + if(args.button == 1) { + if(args.target.type == ObjectType::JUNCTION) { + uuid_ptr j = core.b->get_junction(args.target.path.at(0)); + if(temp_track != nullptr) { + if(temp_track->net && j->net && (temp_track->net->uuid != j->net->uuid)) { + return ToolResponse(); + } + temp_track->to.connect(j); + if(temp_track->net) { + j->net = temp_track->net; + } + else { + temp_track->net = j->net; + } + } + + + create_temp_track(); + temp_junc->net = j->net; + temp_junc->net_segment = j->net_segment; + temp_track->from.connect(j); + temp_track->to.connect(temp_junc); + temp_track->net = j->net; + temp_track->net_segment = j->net_segment; + if(j->net) { + temp_track->width = j->net->net_class->default_width; + } + + + } + else if(args.target.type == ObjectType::PAD) { + auto pkg = &core.b->get_board()->packages.at(args.target.path.at(0)); + auto pad = &pkg->package.pads.at(args.target.path.at(1)); + if(temp_track != nullptr) { + if(temp_track->net && (temp_track->net->uuid != pad->net->uuid)) { + return ToolResponse(); + } + temp_track->to.connect(pkg, pad); + temp_track->net = pad->net; + temp_track->net_segment = pad->net_segment; + } + create_temp_track(); + temp_track->from.connect(pkg, &pkg->package.pads.at(args.target.path.at(1))); + temp_track->to.connect(temp_junc); + temp_track->net = pad->net; + temp_track->net_segment = pad->net_segment; + temp_junc->net = pad->net; + temp_junc->net_segment = pad->net_segment; + if(pad->net) { + temp_track->width = pad->net->net_class->default_width; + } + } + + + else { + Junction *last = temp_junc; + + + /*for(auto it: core.c->get_net_lines()) { + if(it->coord_on_line(temp_junc->position)) { + if(it != temp_line) { + std::cout << "on line" << (std::string)it->uuid << std::endl; + if(temp_junc->bus || it->bus) { //either is bus + if(temp_junc->net || it->net) + return ToolResponse(); //bus-net illegal + + if(temp_junc->bus && it->bus) { //both are bus + if(temp_junc->bus != it->bus) //not the same bus + return ToolResponse(); //illegal + } + else if(temp_junc->bus && !it->bus) { + it->bus = temp_junc->bus; + } + else if(!temp_junc->bus && it->bus) { + temp_junc->bus = it->bus; + } + } + if(temp_junc->net && it->net && it->net.uuid != temp_junc->net->uuid) { + if(merge_nets(it->net, temp_junc->net)) { + return ToolResponse(); + } + } + auto li = core.c->get_sheet()->split_line_net(it, temp_junc); + temp_junc->net = li->net; + temp_junc->bus = li->bus; + break; + } + } + }*/ + temp_junc->temp = false; + temp_junc = core.b->insert_junction(UUID::random()); + temp_junc->temp = true; + temp_junc->position = args.coords; + if(last && temp_track) { + temp_track->net = last->net; + temp_track->net_segment = last->net_segment; + } + if(temp_track) { + temp_junc->net = temp_track->net; + temp_junc->net_segment = temp_track->net_segment; + } + if(last) { + temp_junc->net = last->net; + temp_junc->net_segment = last->net_segment; + } + + create_temp_track(); + temp_track->from.connect(last); + temp_track->to.connect(temp_junc); + temp_track->net = last->net; + temp_track->net_segment = last->net_segment; + if(last->net) { + temp_track->width = last->net->net_class->default_width; + } + } + } + else if(args.button == 3) { + if(temp_track) { + core.b->get_board()->tracks.erase(temp_track->uuid); + temp_track = nullptr; + } + core.b->delete_junction(temp_junc->uuid); + temp_junc = nullptr; + core.b->commit(); + return ToolResponse::end(); + } + } + else if(args.type == ToolEventType::KEY) { + if(args.key == GDK_KEY_Escape) { + core.b->revert(); + return ToolResponse::end(); + } + } + return ToolResponse(); + } + +} diff --git a/core/tool_draw_track.hpp b/core/tool_draw_track.hpp new file mode 100644 index 000000000..273356e4d --- /dev/null +++ b/core/tool_draw_track.hpp @@ -0,0 +1,19 @@ +#pragma once +#include "core.hpp" +#include "track.hpp" + +namespace horizon { + + class ToolDrawTrack: public ToolBase { + public : + ToolDrawTrack(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + + private: + Junction *temp_junc = 0; + Track *temp_track = 0; + Track *create_temp_track(); + }; +} diff --git a/core/tool_edit_component_pin_names.cpp b/core/tool_edit_component_pin_names.cpp new file mode 100644 index 000000000..6363445c9 --- /dev/null +++ b/core/tool_edit_component_pin_names.cpp @@ -0,0 +1,52 @@ +#include "tool_edit_component_pin_names.hpp" +#include "core_schematic.hpp" +#include + +namespace horizon { + + ToolEditComponentPinNames::ToolEditComponentPinNames(Core *c, ToolID tid): ToolBase(c, tid) { + name = "Edit component pin names"; + } + + Component *ToolEditComponentPinNames::get_component() { + Component *comp = nullptr; + for(const auto &it : core.r->selection) { + if(it.type == ObjectType::SCHEMATIC_SYMBOL) { + auto sym = core.c->get_schematic_symbol(it.uuid); + if(comp) { + if(comp != sym->component) { + return nullptr; + } + } + else { + comp = sym->component; + } + } + } + return comp; + } + + bool ToolEditComponentPinNames::can_begin() { + return get_component(); + } + + ToolResponse ToolEditComponentPinNames::begin(const ToolArgs &args) { + std::cout << "tool disconnect\n"; + Component *comp = get_component(); + if(!comp) { + return ToolResponse::end(); + } + bool r = core.r->dialogs.edit_component_pin_names(comp); + if(r) { + core.r->commit(); + } + else { + core.r->revert(); + } + return ToolResponse::end(); + } + ToolResponse ToolEditComponentPinNames::update(const ToolArgs &args) { + return ToolResponse(); + } + +} diff --git a/core/tool_edit_component_pin_names.hpp b/core/tool_edit_component_pin_names.hpp new file mode 100644 index 000000000..3a73d9eee --- /dev/null +++ b/core/tool_edit_component_pin_names.hpp @@ -0,0 +1,19 @@ +#pragma once +#include "core.hpp" +#include "component.hpp" +#include + +namespace horizon { + + class ToolEditComponentPinNames : public ToolBase { + public : + ToolEditComponentPinNames(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + + private: + class Component *get_component(); + + }; +} diff --git a/core/tool_enter_datum.cpp b/core/tool_enter_datum.cpp new file mode 100644 index 000000000..ea5eb5a44 --- /dev/null +++ b/core/tool_enter_datum.cpp @@ -0,0 +1,232 @@ +#include "tool_enter_datum.hpp" +#include "core_padstack.hpp" +#include "core_package.hpp" +#include "polygon.hpp" +#include "accumulator.hpp" +#include +#include + +namespace horizon { + + ToolEnterDatum::ToolEnterDatum(Core *c, ToolID tid): ToolBase(c, tid) { + name = "Enter Datum"; + } + + + ToolResponse ToolEnterDatum::begin(const ToolArgs &args) { + std::cout << "tool enter datum\n"; + bool edge_mode = false; + bool arc_mode = false; + bool hole_mode = false; + bool junction_mode = false; + bool line_mode = false; + bool pad_mode = false; + + Accumulator ai; + Accumulator ac; + + for(const auto &it : args.selection) { + if(it.type == ObjectType::POLYGON_EDGE) { + edge_mode = true; + Polygon *poly = core.r->get_polygon(it.uuid); + auto v1i = it.vertex; + auto v2i = (it.vertex+1)%poly->vertices.size(); + Polygon::Vertex *v1 = &poly->vertices.at(v1i); + Polygon::Vertex *v2 = &poly->vertices.at(v2i); + ai.accumulate(sqrt((v1->position-v2->position).mag_sq())); + } + if(it.type == ObjectType::POLYGON_ARC_CENTER) { + arc_mode = true; + ac.accumulate(core.r->get_polygon(it.uuid)->vertices.at(it.vertex).arc_center); + } + if(it.type == ObjectType::HOLE) { + hole_mode = true; + ac.accumulate(core.r->get_hole(it.uuid)->position); + } + if(it.type == ObjectType::JUNCTION) { + junction_mode = true; + ac.accumulate(core.r->get_junction(it.uuid)->position); + } + if(it.type == ObjectType::LINE) { + line_mode = true; + auto li = core.r->get_line(it.uuid); + auto p0 = li->from->get_position(); + auto p1 = li->to->get_position(); + ai.accumulate(sqrt((p0-p1).mag_sq())); + } + if(it.type == ObjectType::PAD) { + pad_mode = true; + ac.accumulate(core.k->get_package()->pads.at(it.uuid).placement.shift); + } + } + int m_total = edge_mode + arc_mode + hole_mode + junction_mode + line_mode + pad_mode; + if(m_total != 1) { + return ToolResponse::end(); + } + if(edge_mode) { + auto r = core.r->dialogs.ask_datum("Edge length", ai.get()); + if(!r.first) { + return ToolResponse::end(); + } + double l = r.second/2.0; + for(const auto &it : args.selection) { + if(it.type == ObjectType::POLYGON_EDGE) { + Polygon *poly = core.r->get_polygon(it.uuid); + auto v1i = it.vertex; + auto v2i = (it.vertex+1)%poly->vertices.size(); + Polygon::Vertex *v1 = &poly->vertices.at(v1i); + Polygon::Vertex *v2 = &poly->vertices.at(v2i); + Coordi center = (v1->position+v2->position)/2; + Coord half = v2->position-center; + double halflen = sqrt(half.mag_sq()); + double factor = l/halflen; + half *= factor; + Coordi halfi(half.x, half.y); + + const auto &uu = it.uuid; + bool has_v1 = std::find_if(args.selection.cbegin(), args.selection.cend(), [uu, v1i](const auto a){return a.type == ObjectType::POLYGON_VERTEX && a.uuid==uu && a.vertex == v1i;})!=args.selection.cend(); + bool has_v2 = std::find_if(args.selection.cbegin(), args.selection.cend(), [uu, v2i](const auto a){return a.type == ObjectType::POLYGON_VERTEX && a.uuid==uu && a.vertex == v2i;})!=args.selection.cend(); + if(has_v1 && has_v2) { + //nop + } + else if(has_v1 && !has_v2) { + //keep v1, move only v2 + auto t = v2->position; + v2->position = v1->position+halfi*2; + auto d = v2->position - t; + v2->arc_center += d; + } + else if(!has_v1 && has_v2) { + //keep v2, move only v1 + auto t = v1->position; + v1->position = v2->position-halfi*2; + auto d = v1->position - t; + v1->arc_center += d; + } + else if(!has_v1 && !has_v2) { + auto t = v1->position; + v1->position = center-halfi; + auto d = v1->position - t; + v1->arc_center += d; + + t = v2->position; + v2->position = center+halfi; + d = v2->position - t; + v2->arc_center += d; + } + } + } + + } + if(line_mode) { + auto r = core.r->dialogs.ask_datum("Line length", ai.get()); + if(!r.first) { + return ToolResponse::end(); + } + double l = r.second/2.0; + for(const auto &it : args.selection) { + if(it.type == ObjectType::LINE) { + Line *line = core.r->get_line(it.uuid); + Junction *j1 = dynamic_cast(line->from.ptr); + Junction *j2 = dynamic_cast(line->to.ptr); + Coordi center = (j1->position+j2->position)/2; + Coord half = j2->position-center; + double halflen = sqrt(half.mag_sq()); + double factor = l/halflen; + half *= factor; + Coordi halfi(half.x, half.y); + + bool has_v1 = std::find_if(args.selection.cbegin(), args.selection.cend(), [j1](const auto a){return a.type == ObjectType::JUNCTION && a.uuid==j1->uuid;})!=args.selection.cend(); + bool has_v2 = std::find_if(args.selection.cbegin(), args.selection.cend(), [j2](const auto a){return a.type == ObjectType::JUNCTION && a.uuid==j2->uuid;})!=args.selection.cend(); + if(has_v1 && has_v2) { + //nop + } + else if(has_v1 && !has_v2) { + //keep v1, move only v2 + j2->position = j1->position+halfi*2; + } + else if(!has_v1 && has_v2) { + //keep v2, move only v1 + j1->position = j2->position-halfi*2; + } + else if(!has_v1 && !has_v2) { + j1->position = center-halfi; + j2->position = center+halfi; + } + } + } + + } + if(arc_mode) { + auto r = core.r->dialogs.ask_datum("Arc radius"); + if(!r.first) { + return ToolResponse::end(); + } + double l = r.second; + for(const auto &it : args.selection) { + if(it.type == ObjectType::POLYGON_ARC_CENTER) { + Polygon *poly = core.r->get_polygon(it.uuid); + auto v1i = it.vertex; + auto v2i = (it.vertex+1)%poly->vertices.size(); + Polygon::Vertex *v1 = &poly->vertices.at(v1i); + Polygon::Vertex *v2 = &poly->vertices.at(v2i); + Coord r1 = v1->position-v1->arc_center; + Coord r2 = v2->position-v1->arc_center; + r1 *= l/sqrt(r1.mag_sq()); + r2 *= l/sqrt(r2.mag_sq()); + v1->position = v1->arc_center+Coordi(r1.x, r1.y); + v2->position = v1->arc_center+Coordi(r2.x, r2.y); + } + } + } + if(hole_mode) { + auto r = core.r->dialogs.ask_datum_coord("Hole position", ac.get()); + if(!r.first) { + return ToolResponse::end(); + } + for(const auto &it : args.selection) { + if(it.type == ObjectType::HOLE) { + core.r->get_hole(it.uuid)->position = r.second; + } + } + } + if(junction_mode) { + bool r; + Coordi c; + std::pair rc; + std::tie(r, c, rc)= core.r->dialogs.ask_datum_coord2("Junction position", ac.get()); + + if(!r) { + return ToolResponse::end(); + } + for(const auto &it : args.selection) { + if(it.type == ObjectType::JUNCTION) { + if(rc.first) + core.r->get_junction(it.uuid)->position.x = c.x; + if(rc.second) + core.r->get_junction(it.uuid)->position.y = c.y; + } + } + } + if(pad_mode) { + auto r = core.r->dialogs.ask_datum_coord("Pad position", ac.get()); + if(!r.first) { + return ToolResponse::end(); + } + for(const auto &it : args.selection) { + if(it.type == ObjectType::PAD) { + core.k->get_package()->pads.at(it.uuid).placement.shift = r.second; + } + } + } + + + + core.r->commit(); + return ToolResponse::end(); + } + ToolResponse ToolEnterDatum::update(const ToolArgs &args) { + return ToolResponse(); + } + +} diff --git a/core/tool_enter_datum.hpp b/core/tool_enter_datum.hpp new file mode 100644 index 000000000..2bf94abb7 --- /dev/null +++ b/core/tool_enter_datum.hpp @@ -0,0 +1,17 @@ +#pragma once +#include "core.hpp" +#include + +namespace horizon { + + class ToolEnterDatum : public ToolBase { + public : + ToolEnterDatum(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override {return true;} + + private: + + }; +} diff --git a/core/tool_manage_buses.cpp b/core/tool_manage_buses.cpp new file mode 100644 index 000000000..a13a36549 --- /dev/null +++ b/core/tool_manage_buses.cpp @@ -0,0 +1,35 @@ +#include "tool_manage_buses.hpp" +#include "core_schematic.hpp" +#include + +namespace horizon { + + ToolManageBuses::ToolManageBuses(Core *c, ToolID tid): ToolBase(c, tid) { + name = "Bus Manager"; + } + + bool ToolManageBuses::can_begin() { + return core.c; + } + + ToolResponse ToolManageBuses::begin(const ToolArgs &args) { + bool r; + if(tool_id == ToolID::MANAGE_BUSES) { + r = core.r->dialogs.manage_buses(); + } + else if(tool_id == ToolID::ANNOTATE) { + r = core.r->dialogs.annotate(); + } + if(r) { + core.r->commit(); + } + else { + core.r->revert(); + } + return ToolResponse::end(); + } + ToolResponse ToolManageBuses::update(const ToolArgs &args) { + return ToolResponse(); + } + +} diff --git a/core/tool_manage_buses.hpp b/core/tool_manage_buses.hpp new file mode 100644 index 000000000..511198124 --- /dev/null +++ b/core/tool_manage_buses.hpp @@ -0,0 +1,18 @@ +#pragma once +#include "core.hpp" +#include "component.hpp" +#include + +namespace horizon { + + class ToolManageBuses : public ToolBase { + public : + ToolManageBuses(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + + private: + + }; +} diff --git a/core/tool_map_package.cpp b/core/tool_map_package.cpp new file mode 100644 index 000000000..5dea05129 --- /dev/null +++ b/core/tool_map_package.cpp @@ -0,0 +1,55 @@ +#include "tool_map_package.hpp" +#include +#include "core_board.hpp" + +namespace horizon { + + ToolMapPackage::ToolMapPackage(Core *c, ToolID tid): ToolBase(c, tid) { + name = "Map Symbol"; + } + + bool ToolMapPackage::can_begin() { + return core.b; + } + + ToolResponse ToolMapPackage::begin(const ToolArgs &args) { + std::cout << "tool map pkg\n"; + Board *brd = core.b->get_board(); + //collect gates + std::set components; + for(const auto &it: brd->block->components) { + if(it.second.part) + components.emplace(&it.second); + } + + for(const auto &it: brd->packages) { + components.erase(it.second.component); + } + + for(auto it: components) { + std::cout << "can map " << it->refdes << std::endl; + } + + + UUID selected_component; + bool r; + std::tie(r, selected_component) = core.r->dialogs.map_package(components); + if(!r) { + return ToolResponse::end(); + } + Component *comp = &brd->block->components.at(selected_component); + + auto uu = UUID::random(); + BoardPackage *pkg = &brd->packages.emplace(std::make_pair(uu, BoardPackage(uu, comp))).first->second; + pkg->placement.shift = args.coords; + + core.r->selection.clear(); + core.r->selection.emplace(pkg->uuid, ObjectType::BOARD_PACKAGE); + core.r->commit(); + + return ToolResponse::next(ToolID::MOVE); + } + ToolResponse ToolMapPackage::update(const ToolArgs &args) { + return ToolResponse(); + } +} diff --git a/core/tool_map_package.hpp b/core/tool_map_package.hpp new file mode 100644 index 000000000..2e88bfff6 --- /dev/null +++ b/core/tool_map_package.hpp @@ -0,0 +1,14 @@ +#pragma once +#include "core.hpp" + +namespace horizon { + class ToolMapPackage : public ToolBase { + public : + ToolMapPackage(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + + private: + }; +} diff --git a/core/tool_map_pin.cpp b/core/tool_map_pin.cpp new file mode 100644 index 000000000..72ce4deba --- /dev/null +++ b/core/tool_map_pin.cpp @@ -0,0 +1,155 @@ +#include "tool_map_pin.hpp" +#include +#include +#include "core_symbol.hpp" + +namespace horizon { + + ToolMapPin::ToolMapPin(Core *c, ToolID tid): ToolBase(c, tid) { + name = "Map Pin"; + } + + bool ToolMapPin::can_begin() { + return core.y; + } + + void ToolMapPin::create_pin(const UUID &uu) { + auto orientation = Orientation::RIGHT; + if(pin) { + orientation = pin->orientation; + } + pin_last2 = pin_last; + pin_last = pin; + + pin = core.y->insert_symbol_pin(uu); + pin->length = 2.5_mm; + pin->name = core.y->get_symbol()->unit->pins.at(uu).primary_name; + pin->orientation = orientation; + } + + ToolResponse ToolMapPin::begin(const ToolArgs &args) { + std::cout << "tool map pin\n"; + + for(const auto &it : core.y->get_pins()) { + pins.push_back({it, false}); + } + std::sort(pins.begin(), pins.end(), [](const auto &a, const auto &b){return a.first->primary_name < b.first->primary_name;}); + + for(auto &it: pins) { + if(core.y->get_symbol_pin(it.first->uuid)) { + it.second = true; + } + } + if(std::all_of(pins.begin(), pins.end(), [](const auto &a){return a.second;})) { + return ToolResponse::end(); + } + + + bool r; + UUID selected_pin; + std::tie(r, selected_pin) = core.r->dialogs.map_pin(pins); + if(!r) { + return ToolResponse::end(); + } + + auto x = std::find_if(pins.begin(), pins.end(), [selected_pin](const auto &a){return a.first->uuid == selected_pin;}); + assert(x != pins.end()); + pin_index = x-pins.begin(); + + create_pin(selected_pin); + pin->position = args.coords; + + core.r->selection.clear(); + + return ToolResponse(); + } + ToolResponse ToolMapPin::update(const ToolArgs &args) { + if(args.type == ToolEventType::MOVE) { + pin->position = args.coords; + } + else if(args.type == ToolEventType::CLICK) { + if(args.button == 1) { + pins.at(pin_index).second = true; + pin_index++; + while(pin_index < pins.size()) { + if(pins.at(pin_index).second == false) + break; + pin_index++; + } + if(pin_index == pins.size()) { + core.r->commit(); + return ToolResponse::end(); + } + create_pin(pins.at(pin_index).first->uuid); + pin->position = args.coords; + } + else if(args.button == 3) { + if(pin) { + core.y->get_symbol()->pins.erase(pin->uuid); + } + core.r->commit(); + return ToolResponse::end(); + } + } + else if(args.type == ToolEventType::KEY) { + if(args.key == GDK_KEY_Return) { + if(pin_last && pin_last2) { + if(pin_last2->orientation == pin_last->orientation) { + auto shift = pin_last->position - pin_last2->position; + pin->position = pin_last->position + shift; + + pins.at(pin_index).second = true; + pin_index++; + while(pin_index < pins.size()) { + if(pins.at(pin_index).second == false) + break; + pin_index++; + } + if(pin_index == pins.size()) { + core.r->commit(); + return ToolResponse::end(); + } + create_pin(pins.at(pin_index).first->uuid); + pin->position = args.coords; + + } + } + } + else if(args.key == GDK_KEY_space) { + bool r; + UUID selected_pin; + std::tie(r, selected_pin) = core.r->dialogs.map_pin(pins); + if(r) { + core.y->get_symbol()->pins.erase(pin->uuid); + + auto x = std::find_if(pins.begin(), pins.end(), [selected_pin](const auto &a){return a.first->uuid == selected_pin;}); + assert(x != pins.end()); + pin_index = x-pins.begin(); + + auto p1 = pin_last; + auto p2 = pin_last2; + create_pin(selected_pin); + pin->position = args.coords; + pin_last2 = p2; + pin_last = p1; + } + } + else if(args.key == GDK_KEY_r) { + switch(pin->orientation) { + case Orientation::UP: pin->orientation=Orientation::LEFT; break; + case Orientation::DOWN: pin->orientation=Orientation::RIGHT; break; + case Orientation::LEFT: pin->orientation=Orientation::DOWN; break; + case Orientation::RIGHT: pin->orientation=Orientation::UP; break; + } + } + else if(args.key == GDK_KEY_Escape) { + core.r->revert(); + return ToolResponse::end(); + } + } + + return ToolResponse(); + + } + +} diff --git a/core/tool_map_pin.hpp b/core/tool_map_pin.hpp new file mode 100644 index 000000000..d5052dab8 --- /dev/null +++ b/core/tool_map_pin.hpp @@ -0,0 +1,19 @@ +#pragma once +#include "core.hpp" + +namespace horizon { + class ToolMapPin : public ToolBase { + public : + ToolMapPin(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + private: + std::vector> pins; + unsigned int pin_index = 0; + SymbolPin *pin = nullptr; + SymbolPin *pin_last = nullptr; + SymbolPin *pin_last2 = nullptr; + void create_pin(const UUID &uu); + }; +} diff --git a/core/tool_map_symbol.cpp b/core/tool_map_symbol.cpp new file mode 100644 index 000000000..8df1a4ac1 --- /dev/null +++ b/core/tool_map_symbol.cpp @@ -0,0 +1,89 @@ +#include "tool_map_symbol.hpp" +#include +#include "core_schematic.hpp" + +namespace horizon { + + ToolMapSymbol::ToolMapSymbol(Core *c, ToolID tid): ToolBase(c, tid) { + name = "Map Symbol"; + } + + bool ToolMapSymbol::can_begin() { + return core.c; + } + + ToolResponse ToolMapSymbol::begin(const ToolArgs &args) { + std::cout << "tool map sym\n"; + Schematic *sch = core.c->get_schematic(); + //collect gates + std::set> gates; + for(const auto &it_component: sch->block->components) { + for(const auto &it_gate: it_component.second.entity->gates) { + gates.emplace(it_component.first, it_gate.first); + } + } + + for(auto &it_sheet: sch->sheets) { + Sheet &sheet = it_sheet.second; + + for(const auto &it_sym: sheet.symbols) { + const auto &sym = it_sym.second; + if(gates.count({sym.component->uuid, sym.gate->uuid})) { + gates.erase({sym.component->uuid, sym.gate->uuid}); + } + } + } + UUID filter_uuid; + if(core.c->selection.size() == 1) { + if(core.c->selection.begin()->type == ObjectType::COMPONENT) { + filter_uuid = core.c->selection.begin()->uuid; + } + } + std::map, std::string> gates_out; + for(const auto &it: gates) { + Component *comp = &sch->block->components.at(it.at(0)); + Gate *gate = &comp->entity->gates.at(it.at(1)); + if(!filter_uuid || filter_uuid==comp->uuid) { + gates_out.emplace(std::make_pair(it, comp->refdes+gate->suffix)); + } + } + if(gates_out.size() == 0) { + return ToolResponse::end(); + } + + UUIDPath<2> selected_gate; + bool r; + if(gates_out.size()>1) { + std::tie(r, selected_gate) = core.r->dialogs.map_symbol(gates_out); + if(!r) { + return ToolResponse::end(); + } + } + else { + selected_gate = gates_out.begin()->first; + } + Component *comp = &sch->block->components.at(selected_gate.at(0)); + Gate *gate = &comp->entity->gates.at(selected_gate.at(1)); + UUID selected_symbol; + std::tie(r, selected_symbol) = core.r->dialogs.select_symbol(gate->unit->uuid); + if(!r) { + return ToolResponse::end(); + } + + Symbol *sym = core.c->m_pool->get_symbol(selected_symbol); + SchematicSymbol *schsym = core.c->insert_schematic_symbol(UUID::random(), sym); + schsym->component = comp; + schsym->gate = gate; + schsym->placement.shift = args.coords; + + core.c->selection.clear(); + core.c->selection.emplace(schsym->uuid, ObjectType::SCHEMATIC_SYMBOL); + core.c->commit(); + + + return ToolResponse::next(ToolID::MOVE); + } + ToolResponse ToolMapSymbol::update(const ToolArgs &args) { + return ToolResponse(); + } +} diff --git a/core/tool_map_symbol.hpp b/core/tool_map_symbol.hpp new file mode 100644 index 000000000..d67e1e2d1 --- /dev/null +++ b/core/tool_map_symbol.hpp @@ -0,0 +1,13 @@ +#pragma once +#include "core.hpp" + +namespace horizon { + class ToolMapSymbol : public ToolBase { + public : + ToolMapSymbol(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + private: + }; +} diff --git a/core/tool_move.cpp b/core/tool_move.cpp new file mode 100644 index 000000000..a92030d59 --- /dev/null +++ b/core/tool_move.cpp @@ -0,0 +1,389 @@ +#include "tool_move.hpp" +#include "core_schematic.hpp" +#include "core_symbol.hpp" +#include "core_padstack.hpp" +#include "core_package.hpp" +#include "core_board.hpp" +#include "accumulator.hpp" +#include + +namespace horizon { + + ToolMove::ToolMove(Core *c, ToolID tid): ToolBase(c, tid) { + name = "Move"; + } + + void ToolMove::expand_selection() { + std::set new_sel; + for(const auto &it : core.r->selection) { + switch(it.type) { + case ObjectType::LINE : { + Line *line = core.r->get_line(it.uuid); + new_sel.emplace(line->from.uuid, ObjectType::JUNCTION); + new_sel.emplace(line->to.uuid, ObjectType::JUNCTION); + } break; + case ObjectType::POLYGON_EDGE : { + Polygon *poly = core.r->get_polygon(it.uuid); + auto vs = poly->get_vertices_for_edge(it.vertex); + new_sel.emplace(poly->uuid, ObjectType::POLYGON_VERTEX, vs.first); + new_sel.emplace(poly->uuid, ObjectType::POLYGON_VERTEX, vs.second); + } break; + + case ObjectType::NET_LABEL : { + auto &la = core.c->get_sheet()->net_labels.at(it.uuid); + new_sel.emplace(la.junction->uuid, ObjectType::JUNCTION); + } break; + case ObjectType::BUS_LABEL : { + auto &la = core.c->get_sheet()->bus_labels.at(it.uuid); + new_sel.emplace(la.junction->uuid, ObjectType::JUNCTION); + } break; + case ObjectType::POWER_SYMBOL : { + auto &ps = core.c->get_sheet()->power_symbols.at(it.uuid); + new_sel.emplace(ps.junction->uuid, ObjectType::JUNCTION); + } break; + case ObjectType::BUS_RIPPER : { + auto &rip = core.c->get_sheet()->bus_rippers.at(it.uuid); + new_sel.emplace(rip.junction->uuid, ObjectType::JUNCTION); + } break; + + case ObjectType::LINE_NET : { + auto line = &core.c->get_sheet()->net_lines.at(it.uuid); + for(auto &it_ft: {line->from, line->to}) { + if(it_ft.is_junc()) { + new_sel.emplace(it_ft.junc.uuid, ObjectType::JUNCTION); + } + } + } break; + case ObjectType::TRACK : { + auto track = &core.b->get_board()->tracks.at(it.uuid); + for(auto &it_ft: {track->from, track->to}) { + if(it_ft.is_junc()) { + new_sel.emplace(it_ft.junc.uuid, ObjectType::JUNCTION); + } + } + } break; + case ObjectType::VIA : { + auto via = &core.b->get_board()->vias.at(it.uuid); + new_sel.emplace(via->junction->uuid, ObjectType::JUNCTION); + } break; + case ObjectType::POLYGON : { + auto poly = core.r->get_polygon(it.uuid); + int i = 0; + for(const auto &itv: poly->vertices) { + new_sel.emplace(poly->uuid, ObjectType::POLYGON_VERTEX, i); + i++; + } + } break; + + case ObjectType::ARC : { + Arc *arc = core.r->get_arc(it.uuid); + new_sel.emplace(arc->from.uuid, ObjectType::JUNCTION); + new_sel.emplace(arc->to.uuid, ObjectType::JUNCTION); + new_sel.emplace(arc->center.uuid, ObjectType::JUNCTION); + } break; + + case ObjectType::SCHEMATIC_SYMBOL : { + auto sym = core.c->get_schematic_symbol(it.uuid); + for(const auto &itt: sym->texts) { + new_sel.emplace(itt->uuid, ObjectType::TEXT); + } + } break; + + default:; + } + } + core.r->selection.insert(new_sel.begin(), new_sel.end()); + } + + void ToolMove::transform(Coordi &a, const Coordi ¢er, bool rotate) { + int64_t dx = a.x - center.x; + int64_t dy = a.y - center.y; + if(rotate) { + a.x = center.x + dy; + a.y = center.y - dx; + } + else { + a.x = center.x - dx; + a.y = center.y + dy; + } + } + + static Orientation transform_orienation(Orientation orientation, bool rotate, bool reverse=false) { + Orientation new_orientation; + if(rotate) { + if(!reverse) { + switch(orientation) { + case Orientation::UP: new_orientation=Orientation::RIGHT; break; + case Orientation::DOWN: new_orientation=Orientation::LEFT; break; + case Orientation::LEFT: new_orientation=Orientation::UP; break; + case Orientation::RIGHT: new_orientation=Orientation::DOWN; break; + } + } + else { + switch(orientation) { + case Orientation::UP: new_orientation=Orientation::LEFT; break; + case Orientation::DOWN: new_orientation=Orientation::RIGHT; break; + case Orientation::LEFT: new_orientation=Orientation::DOWN; break; + case Orientation::RIGHT: new_orientation=Orientation::UP; break; + } + } + } + else { + switch(orientation) { + case Orientation::UP: new_orientation=Orientation::UP; break; + case Orientation::DOWN: new_orientation=Orientation::DOWN; break; + case Orientation::LEFT: new_orientation=Orientation::RIGHT; break; + case Orientation::RIGHT: new_orientation=Orientation::LEFT; break; + } + } + return new_orientation; + } + + void ToolMove::mirror_or_rotate(const Coordi ¢er,bool rotate) { + for(const auto &it: core.r->selection) { + switch(it.type) { + case ObjectType::JUNCTION : + transform(core.r->get_junction(it.uuid)->position, center, rotate); + break; + case ObjectType::POLYGON_VERTEX : + transform(core.r->get_polygon(it.uuid)->vertices.at(it.vertex).position, center, rotate); + break; + case ObjectType::POLYGON_ARC_CENTER : + transform(core.r->get_polygon(it.uuid)->vertices.at(it.vertex).arc_center, center, rotate); + if(!rotate) { + core.r->get_polygon(it.uuid)->vertices.at(it.vertex).arc_reverse ^= 1; + } + break; + + case ObjectType::SYMBOL_PIN: { + SymbolPin *pin = core.y->get_symbol_pin(it.uuid); + transform(pin->position, center, rotate); + pin->orientation = transform_orienation(pin->orientation, rotate); + + } break; + + case ObjectType::TEXT: { + Text *txt = core.r->get_text(it.uuid); + transform(txt->position, center, rotate); + bool rev = core.r->get_layers().at(txt->layer).reverse; + txt->orientation = transform_orienation(txt->orientation, rotate, rev); + } break; + + case ObjectType::ARC : + if(!rotate) { + core.r->get_arc(it.uuid)->reverse(); + } + break; + case ObjectType::POWER_SYMBOL : + if(!rotate) { + auto &x = core.c->get_sheet()->power_symbols.at(it.uuid).mirror; + x = !x; + } + break; + + case ObjectType::SCHEMATIC_SYMBOL : { + SchematicSymbol *sym = core.c->get_schematic_symbol(it.uuid); + transform(sym->placement.shift, center, rotate); + if(rotate) { + if(sym->placement.mirror) { + sym->placement.angle = (sym->placement.angle+16384)%65536; + } + else { + sym->placement.angle = (sym->placement.angle+(65536-16384))%65536; + } + } + else { + sym->placement.mirror = !sym->placement.mirror; + } + + }break; + + case ObjectType::BOARD_PACKAGE : { + BoardPackage *pkg = &core.b->get_board()->packages.at(it.uuid); + if(rotate) { + transform(pkg->placement.shift, center, rotate); + // if(sym->placement.mirror) { + // sym->placement.angle = (sym->placement.angle+16384)%65536; + // } + //else { + pkg->placement.angle = (pkg->placement.angle+(65536-16384))%65536; + // } + } + + }break; + + + + case ObjectType::PAD : { + Pad *pad = &core.k->get_package()->pads.at(it.uuid); + transform(pad->placement.shift, center, rotate); + if(rotate) { + pad->placement.angle = (pad->placement.angle+(65536-16384))%65536; + } + }break; + + case ObjectType::NET_LABEL : { + auto sheet = core.c->get_sheet(); + auto *label = &sheet->net_labels.at(it.uuid); + label->orientation = transform_orienation(label->orientation, rotate); + } break; + case ObjectType::BUS_LABEL : { + auto sheet = core.c->get_sheet(); + auto *label = &sheet->bus_labels.at(it.uuid); + label->orientation = transform_orienation(label->orientation, rotate); + } break; + default:; + } + } + } + + void ToolMove::update_selection_center() { + Accumulator accu; + for(const auto &it:core.r->selection) { + switch(it.type) { + case ObjectType::JUNCTION : + accu.accumulate(core.r->get_junction(it.uuid)->position); + break; + case ObjectType::HOLE : + accu.accumulate(core.r->get_hole(it.uuid)->position); + break; + case ObjectType::SYMBOL_PIN : + accu.accumulate(core.y->get_symbol_pin(it.uuid)->position); + break; + case ObjectType::SCHEMATIC_SYMBOL : + accu.accumulate(core.c->get_schematic_symbol(it.uuid)->placement.shift); + break; + case ObjectType::BOARD_PACKAGE : + accu.accumulate(core.b->get_board()->packages.at(it.uuid).placement.shift); + break; + case ObjectType::PAD : + accu.accumulate(core.k->get_package()->pads.at(it.uuid).placement.shift); + break; + case ObjectType::TEXT : + accu.accumulate(core.r->get_text(it.uuid)->position); + break; + case ObjectType::POLYGON_VERTEX : + accu.accumulate(core.r->get_polygon(it.uuid)->vertices.at(it.vertex).position); + break; + case ObjectType::POLYGON_ARC_CENTER : + accu.accumulate(core.r->get_polygon(it.uuid)->vertices.at(it.vertex).arc_center); + break; + default:; + + } + } + if(core.c || core.y) + selection_center = (accu.get()/1.25_mm)*1.25_mm; + else + selection_center = accu.get(); + } + + ToolResponse ToolMove::begin(const ToolArgs &args) { + std::cout << "tool move\n"; + last = args.coords; + + if(!can_begin()) { + return ToolResponse::end(); + } + update_selection_center(); + + + + if(tool_id == ToolID::ROTATE || tool_id == ToolID::MIRROR) { + mirror_or_rotate(selection_center, tool_id==ToolID::ROTATE); + core.r->commit(); + return ToolResponse::end(); + } + if(tool_id == ToolID::MOVE_EXACTLY) { + auto r = core.r->dialogs.ask_datum_coord("Move exactly"); + if(!r.first) { + return ToolResponse::end(); + } + do_move(r.second); + core.r->commit(); + return ToolResponse::end(); + } + + return ToolResponse(); + } + + + bool ToolMove::can_begin() { + expand_selection(); + return core.r->selection.size()>0; + } + + void ToolMove::do_move(const Coordi &delta) { + for(const auto &it:core.r->selection) { + switch(it.type) { + case ObjectType::JUNCTION : + core.r->get_junction(it.uuid)->position += delta; + break; + case ObjectType::HOLE : + core.r->get_hole(it.uuid)->position += delta; + break; + case ObjectType::SYMBOL_PIN : + core.y->get_symbol_pin(it.uuid)->position += delta; + break; + case ObjectType::SCHEMATIC_SYMBOL : + core.c->get_schematic_symbol(it.uuid)->placement.shift += delta; + break; + case ObjectType::BOARD_PACKAGE : + core.b->get_board()->packages.at(it.uuid).placement.shift += delta; + break; + case ObjectType::PAD : + core.k->get_package()->pads.at(it.uuid).placement.shift += delta; + break; + case ObjectType::TEXT : + core.r->get_text(it.uuid)->position += delta; + break; + case ObjectType::POLYGON_VERTEX : + core.r->get_polygon(it.uuid)->vertices.at(it.vertex).position += delta; + break; + case ObjectType::POLYGON_ARC_CENTER : + core.r->get_polygon(it.uuid)->vertices.at(it.vertex).arc_center += delta; + break; + default:; + + } + } + } + + ToolResponse ToolMove::update(const ToolArgs &args) { + if(args.type == ToolEventType::MOVE) { + Coordi delta = args.coords - last; + do_move(delta); + last = args.coords; + if(core.b) { + core.b->get_board()->update_airwires(); + } + } + else if(args.type == ToolEventType::CLICK) { + if(args.button == 1) { + for(const auto &it:core.r->selection) { + if(it.type == ObjectType::SCHEMATIC_SYMBOL) { + core.c->get_schematic()->autoconnect_symbol(core.c->get_sheet(), core.c->get_schematic_symbol(it.uuid)); + } + } + core.r->commit(); + } + else { + core.r->revert(); + } + return ToolResponse::end(); + } + else if(args.type == ToolEventType::KEY) { + if(args.key == GDK_KEY_Escape) { + core.r->revert(); + return ToolResponse::end(); + } + else if(args.key == GDK_KEY_r || args.key == GDK_KEY_e) { + bool rotate = args.key == GDK_KEY_r; + update_selection_center(); + mirror_or_rotate(selection_center, rotate); + } + } + return ToolResponse(); + } + +} diff --git a/core/tool_move.hpp b/core/tool_move.hpp new file mode 100644 index 000000000..e4118c5b9 --- /dev/null +++ b/core/tool_move.hpp @@ -0,0 +1,24 @@ +#pragma once +#include "core.hpp" + +namespace horizon { + + class ToolMove : public ToolBase { + public : + ToolMove(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override ; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + + + private: + Coordi last; + Coordi selection_center; + void update_selection_center(); + void expand_selection(); + void transform(Coordi &a, const Coordi ¢er, bool rotate); + void mirror_or_rotate(const Coordi ¢er, bool rotate); + void do_move(const Coordi &delta); + + }; +} diff --git a/core/tool_move_net_segment.cpp b/core/tool_move_net_segment.cpp new file mode 100644 index 000000000..cf120f2bd --- /dev/null +++ b/core/tool_move_net_segment.cpp @@ -0,0 +1,102 @@ +#include "tool_move_net_segment.hpp" +#include "tool_place_power_symbol.hpp" +#include +#include "core_schematic.hpp" + +namespace horizon { + + ToolMoveNetSegment::ToolMoveNetSegment(Core *c, ToolID tid): ToolBase(c, tid) { + name = "Move net segment"; + } + + bool ToolMoveNetSegment::can_begin() { + if(!core.c) { + return false; + } + return get_net_segment(); + } + + UUID ToolMoveNetSegment::get_net_segment() { + for(const auto &it: core.r->selection) { + UUID this_ns; + if(it.type == ObjectType::JUNCTION) { + this_ns = core.c->get_junction(it.uuid)->net_segment; + } + else if(it.type == ObjectType::LINE_NET) { + this_ns = core.c->get_sheet()->net_lines.at(it.uuid).net_segment; + } + else if(it.type == ObjectType::POWER_SYMBOL) { + this_ns = core.c->get_sheet()->power_symbols.at(it.uuid).junction->net_segment; + } + else if(it.type == ObjectType::NET_LABEL) { + this_ns = core.c->get_sheet()->net_labels.at(it.uuid).junction->net_segment; + } + if(this_ns && !net_segment) { + net_segment = this_ns; + } + if(this_ns && net_segment) { + if(this_ns != net_segment) { + return UUID(); + } + } + } + return net_segment; + } + + ToolResponse ToolMoveNetSegment::begin(const ToolArgs &args) { + std::cout << "tool select net seg\n"; + net_segment = get_net_segment(); + core.c->selection.clear(); + if(!net_segment) { + return ToolResponse::end(); + } + auto nsinfo = core.c->get_sheet()->analyze_net_segments().at(net_segment); + if(nsinfo.bus) + return ToolResponse::end(); + + for(const auto &it: core.c->get_sheet()->junctions) { + if(it.second.net_segment == net_segment) { + core.c->selection.emplace(it.first, ObjectType::JUNCTION); + } + } + for(const auto &it: core.c->get_sheet()->net_lines) { + if(it.second.net_segment == net_segment) { + core.c->selection.emplace(it.first, ObjectType::LINE_NET); + } + } + if(tool_id == ToolID::SELECT_NET_SEGMENT) + return ToolResponse::end(); + + if(tool_id == ToolID::MOVE_NET_SEGMENT_NEW || tool_id == ToolID::MOVE_NET_SEGMENT) { + if(nsinfo.has_power_sym) { + return ToolResponse::end(); + } + } + + if(tool_id == ToolID::MOVE_NET_SEGMENT_NEW) { + Net *net = core.c->get_schematic()->block->insert_net(); + auto pins = core.c->get_sheet()->get_pins_connected_to_net_segment(net_segment); + core.c->get_schematic()->block->extract_pins(pins, net); + core.c->commit(); + + return ToolResponse::end(); + } + + bool r; + UUID net_uuid; + std::tie(r, net_uuid) = core.r->dialogs.select_net(false); + if(!r) { + return ToolResponse::end(); + } + Net *net = &core.c->get_schematic()->block->nets.at(net_uuid); + auto pins = core.c->get_sheet()->get_pins_connected_to_net_segment(net_segment); + core.c->get_schematic()->block->extract_pins(pins, net); + core.c->commit(); + return ToolResponse::end(); + + } + ToolResponse ToolMoveNetSegment::update(const ToolArgs &args) { + return ToolResponse(); + } + +} diff --git a/core/tool_move_net_segment.hpp b/core/tool_move_net_segment.hpp new file mode 100644 index 000000000..e1db5ff91 --- /dev/null +++ b/core/tool_move_net_segment.hpp @@ -0,0 +1,18 @@ +#pragma once +#include "core.hpp" + +namespace horizon { + + class ToolMoveNetSegment : public ToolBase { + public : + ToolMoveNetSegment(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + + private: + UUID net_segment; + UUID get_net_segment(); + + }; +} diff --git a/core/tool_paste.cpp b/core/tool_paste.cpp new file mode 100644 index 000000000..9c7029d3d --- /dev/null +++ b/core/tool_paste.cpp @@ -0,0 +1,143 @@ +#include "tool_paste.hpp" +#include +#include "json.hpp" +#include "core_package.hpp" + +namespace horizon { + + ToolPaste::ToolPaste(Core *c, ToolID tid): ToolBase(c, tid) { + name = "Paste"; + } + + class JunctionProvider: public Object { + public: + JunctionProvider(Core *c, const std::map &xj):core(c), junction_xlat(xj) {} + virtual ~JunctionProvider() {} + + Junction *get_junction(const UUID &uu) override { + return core->get_junction(junction_xlat.at(uu)); + } + + + private: + Core *core; + const std::map &junction_xlat; + }; + + void ToolPaste::fix_layer(int &la) { + if(core.r->get_layers().count(la) == 0) { + la = 0; + } + } + + void ToolPaste::apply_shift(Coordi &c, const Coordi &cursor_pos) { + c += shift; + } + + ToolResponse ToolPaste::begin(const ToolArgs &args) { + ref_clipboard = Gtk::Clipboard::get(); + //ref_clipboard->request_contents("imp-buffer", + // sigc::mem_fun(this, &ToolPaste::on_clipboard_received) ); + auto seld = ref_clipboard->wait_for_contents("imp-buffer"); + if(seld.gobj()) + std::cout << "len " << seld.get_length() <>(); + core.r->selection.clear(); + shift = args.coords - cursor_pos; + if(j.count("texts")){ + const json &o = j["texts"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID::random(); + auto x = core.r->insert_text(u); + *x = Text(u, it.value()); + fix_layer(x->layer); + apply_shift(x->position, args.coords); + core.r->selection.emplace(u, ObjectType::TEXT); + } + } + std::map junction_xlat; + if(j.count("junctions")){ + const json &o = j["junctions"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID::random(); + junction_xlat.emplace(it.key(), u); + auto x = core.r->insert_junction(u); + *x = Junction(u, it.value()); + apply_shift(x->position, args.coords); + core.r->selection.emplace(u, ObjectType::JUNCTION); + } + } + if(j.count("lines")){ + const json &o = j["lines"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID::random(); + auto x = core.r->insert_line(u); + JunctionProvider p(core.r, junction_xlat); + *x = Line(u, it.value(), p); + fix_layer(x->layer); + core.r->selection.emplace(u, ObjectType::LINE); + } + } + if(j.count("arcs")){ + const json &o = j["arcs"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID::random(); + auto x = core.r->insert_arc(u); + JunctionProvider p(core.r, junction_xlat); + *x = Arc(u, it.value(), p); + fix_layer(x->layer); + core.r->selection.emplace(u, ObjectType::ARC); + } + } + if(j.count("pads") && core.k){ + const json &o = j["pads"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID::random(); + auto &x = core.k->get_package()->pads.emplace(u, Pad(u, it.value(), *core.r->m_pool)).first->second; + apply_shift(x.placement.shift, args.coords); + core.r->selection.emplace(u, ObjectType::PAD); + } + } + if(j.count("holes")){ + const json &o = j["holes"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID::random(); + auto x = core.r->insert_hole(u); + *x = Hole(u, it.value()); + apply_shift(x->position, args.coords); + core.r->selection.emplace(u, ObjectType::HOLE); + } + } + if(j.count("polygons")){ + const json &o = j["polygons"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID::random(); + auto x = core.r->insert_polygon(u); + *x = Polygon(u, it.value()); + for(auto &itv: x->vertices) { + itv.arc_center += shift; + itv.position += shift; + } + core.r->selection.emplace(u, ObjectType::POLYGON); + } + } + core.r->commit(); + return ToolResponse::next(ToolID::MOVE); + } + + void ToolPaste::on_clipboard_received(const Gtk::SelectionData& selection_data) { + auto clipboard_data = selection_data.get_data_as_string(); + auto targ = selection_data.get_length(); + std::cout << "paste " << targ <<":" << clipboard_data << std::endl; + } + + + ToolResponse ToolPaste::update(const ToolArgs &args) { + return ToolResponse(); + } + +} diff --git a/core/tool_paste.hpp b/core/tool_paste.hpp new file mode 100644 index 000000000..086098583 --- /dev/null +++ b/core/tool_paste.hpp @@ -0,0 +1,22 @@ +#pragma once +#include "core.hpp" +#include + +namespace horizon { + + class ToolPaste : public ToolBase { + public : + ToolPaste(Core *c, ToolID tid); + virtual ToolResponse begin(const ToolArgs &args); + virtual ToolResponse update(const ToolArgs &args); + + private: + void on_clipboard_received(const Gtk::SelectionData& selection_data); + Glib::RefPtr ref_clipboard; + void fix_layer(int &la); + void apply_shift(Coordi &c, const Coordi &cursor_pos); + Coordi shift; + bool shift_set = false; + + }; +} diff --git a/core/tool_place_bus_label.cpp b/core/tool_place_bus_label.cpp new file mode 100644 index 000000000..71d0a77ba --- /dev/null +++ b/core/tool_place_bus_label.cpp @@ -0,0 +1,80 @@ +#include "tool_place_bus_label.hpp" +#include +#include "core_schematic.hpp" + +namespace horizon { + + ToolPlaceBusLabel::ToolPlaceBusLabel(Core *c, ToolID tid):ToolPlaceJunction(c, tid) { + name = "Place bus label"; + } + + bool ToolPlaceBusLabel::can_begin() { + return core.c; + } + + bool ToolPlaceBusLabel::begin_attached() { + bool r; + UUID net_uuid; + std::tie(r, net_uuid) = core.r->dialogs.select_bus(); + if(!r) { + return false; + } + bus = &core.c->get_schematic()->block->buses.at(net_uuid); + return true; + } + + void ToolPlaceBusLabel::create_attached() { + auto uu = UUID::random(); + la = &core.c->get_sheet()->bus_labels.emplace(uu, uu).first->second; + la->bus =bus; + la->junction = temp; + temp->bus = bus; + } + + void ToolPlaceBusLabel::delete_attached() { + if(la) { + core.c->get_sheet()->bus_labels.erase(la->uuid); + temp->net = nullptr; + } + } + + bool ToolPlaceBusLabel::check_line(LineNet *li) { + if(li->net) + return false; + if(!li->bus) + return true; + if(li->bus != bus) + return false; + return true; + } + + bool ToolPlaceBusLabel::update_attached(const ToolArgs &args) { + if(args.type == ToolEventType::CLICK) { + if(args.button == 1) { + if(args.target.type == ObjectType::JUNCTION) { + Junction *j = core.r->get_junction(args.target.path.at(0)); + if(j->net) + return true; + if(j->bus && j->bus != bus) + return true; + la->junction = j; + j->bus = bus; + create_attached(); + return true; + } + } + } + /*else if(args.type == ToolEventType::KEY) { + if(la) { + if(args.key == GDK_KEY_r) { + la->orientation = transform_orienation(la->orientation, true); + last_orientation = la->orientation; + return true; + } + } + }*/ + + return false; + } + +} diff --git a/core/tool_place_bus_label.hpp b/core/tool_place_bus_label.hpp new file mode 100644 index 000000000..c3b694535 --- /dev/null +++ b/core/tool_place_bus_label.hpp @@ -0,0 +1,26 @@ +#pragma once +#include "core.hpp" +#include "bus.hpp" +#include "tool_place_junction.hpp" +#include "bus_label.hpp" +#include + +namespace horizon { + + class ToolPlaceBusLabel : public ToolPlaceJunction { + public : + ToolPlaceBusLabel(Core *c, ToolID tid); + bool can_begin() override; + + private: + void create_attached(); + void delete_attached(); + bool begin_attached(); + bool update_attached(const ToolArgs &args); + bool check_line(LineNet *li); + BusLabel *la= nullptr; + std::forward_list labels_placed; + Bus* bus = nullptr; + + }; +} diff --git a/core/tool_place_bus_ripper.cpp b/core/tool_place_bus_ripper.cpp new file mode 100644 index 000000000..b84c15f50 --- /dev/null +++ b/core/tool_place_bus_ripper.cpp @@ -0,0 +1,98 @@ +#include "tool_place_bus_ripper.hpp" +#include +#include "core_schematic.hpp" + +namespace horizon { + + ToolPlaceBusRipper::ToolPlaceBusRipper(Core *c, ToolID tid): ToolBase(c, tid) { + name = "Place Bus Ripper"; + } + + bool ToolPlaceBusRipper::can_begin() { + return core.c; + } + + + ToolResponse ToolPlaceBusRipper::begin(const ToolArgs &args) { + std::cout << "tool place bus ripper\n"; + return ToolResponse(); + } + + ToolResponse ToolPlaceBusRipper::update(const ToolArgs &args) { + if(args.type == ToolEventType::CLICK) { + if(args.button == 1) { + if(args.target.type == ObjectType::JUNCTION) { + Junction *j = core.c->get_junction(args.target.path.at(0)); + if(!j->bus) + return ToolResponse(); + Bus *bus = j->bus; + bool r; + UUID bus_member_uuid; + + std::tie(r, bus_member_uuid) = core.r->dialogs.select_bus_member(bus->uuid); + if(!r) + return ToolResponse(); + Bus::Member *bus_member = &bus->members.at(bus_member_uuid); + + auto uu = UUID::random(); + BusRipper *rip = &core.c->get_sheet()->bus_rippers.emplace(uu, uu).first->second; + rip->bus = bus; + rip->bus_member = bus_member; + rip->junction = j; + } + else if(args.target.type == ObjectType::INVALID) { + for(auto it: core.c->get_net_lines()) { + if(it->coord_on_line(args.coords)) { + std::cout << "on line" << std::endl; + if(!it->bus) + return ToolResponse(); + Bus *bus = it->bus; + bool r; + UUID bus_member_uuid; + + std::tie(r, bus_member_uuid) = core.r->dialogs.select_bus_member(bus->uuid); + if(!r) + return ToolResponse(); + Bus::Member *bus_member = &bus->members.at(bus_member_uuid); + + Junction *j = core.c->insert_junction(UUID::random()); + j->position = args.coords; + j->bus = bus; + + core.c->get_sheet()->split_line_net(it, j); + + auto uu = UUID::random(); + BusRipper *rip = &core.c->get_sheet()->bus_rippers.emplace(uu, uu).first->second; + rip->bus = bus; + rip->bus_member = bus_member; + rip->junction = j; + + + break; + } + } + + + } + } + else if(args.button == 3) { + //core.c->get_sheet()->power_symbols.erase(temp->uuid); + //temp = 0; + core.c->commit(); + core.c->selection.clear(); + //for(auto it: symbols_placed) { + // core.c->selection.emplace(it->uuid, ObjectType::POWER_SYMBOL); + //} + return ToolResponse::end(); + } + } + else if(args.type == ToolEventType::KEY) { + if(args.key == GDK_KEY_Escape) { + core.c->revert(); + return ToolResponse::end(); + } + } + return ToolResponse(); + } + +} diff --git a/core/tool_place_bus_ripper.hpp b/core/tool_place_bus_ripper.hpp new file mode 100644 index 000000000..dee35baff --- /dev/null +++ b/core/tool_place_bus_ripper.hpp @@ -0,0 +1,17 @@ +#pragma once +#include "core.hpp" +#include + +namespace horizon { + + + class ToolPlaceBusRipper : public ToolBase { + public : + ToolPlaceBusRipper(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override ; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + + private: + }; +} diff --git a/core/tool_place_hole.cpp b/core/tool_place_hole.cpp new file mode 100644 index 000000000..33d4a04ca --- /dev/null +++ b/core/tool_place_hole.cpp @@ -0,0 +1,58 @@ +#include "tool_place_hole.hpp" +#include +#include "core_padstack.hpp" + +namespace horizon { + + ToolPlaceHole::ToolPlaceHole(Core *c, ToolID tid):ToolBase(c, tid) { + name = "Place Hole"; + } + + bool ToolPlaceHole::can_begin() { + return core.r->has_object_type(ObjectType::HOLE); + } + + ToolResponse ToolPlaceHole::begin(const ToolArgs &args) { + std::cout << "tool place hole\n"; + + create_hole(args.coords); + + return ToolResponse(); + } + + void ToolPlaceHole::create_hole(const Coordi &c) { + temp = core.r->insert_hole(UUID::random()); + temp->position = c; + } + + ToolResponse ToolPlaceHole::update(const ToolArgs &args) { + if(args.type == ToolEventType::MOVE) { + temp->position = args.coords; + } + else if(args.type == ToolEventType::CLICK) { + if(args.button == 1) { + holes_placed.push_front(temp); + + create_hole(args.coords); + } + else if(args.button == 3) { + core.r->delete_hole(temp->uuid); + temp = 0; + core.r->commit(); + core.r->selection.clear(); + for(auto it: holes_placed) { + core.r->selection.emplace(it->uuid, ObjectType::HOLE); + } + return ToolResponse::end(); + } + } + else if(args.type == ToolEventType::KEY) { + if(args.key == GDK_KEY_Escape) { + core.r->revert(); + return ToolResponse::end(); + } + } + return ToolResponse(); + } + +} diff --git a/core/tool_place_hole.hpp b/core/tool_place_hole.hpp new file mode 100644 index 000000000..26ad61409 --- /dev/null +++ b/core/tool_place_hole.hpp @@ -0,0 +1,22 @@ +#pragma once +#include "hole.hpp" +#include "core.hpp" +#include + +namespace horizon { + + class ToolPlaceHole: public ToolBase { + public : + ToolPlaceHole (Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override ; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + + protected: + Hole *temp = 0; + std::forward_list holes_placed; + + + void create_hole(const Coordi &c); + }; +} diff --git a/core/tool_place_junction.cpp b/core/tool_place_junction.cpp new file mode 100644 index 000000000..3b23cf0c7 --- /dev/null +++ b/core/tool_place_junction.cpp @@ -0,0 +1,85 @@ +#include "tool_place_junction.hpp" +#include +#include "core_schematic.hpp" +#include "core_symbol.hpp" + +namespace horizon { + + ToolPlaceJunction::ToolPlaceJunction(Core *c, ToolID tid):ToolBase(c, tid) { + name = "Place Junction"; + } + + bool ToolPlaceJunction::can_begin() { + return core.r->has_object_type(ObjectType::JUNCTION); + } + + + ToolResponse ToolPlaceJunction::begin(const ToolArgs &args) { + std::cout << "tool place junction\n"; + + if(!begin_attached()) { + return ToolResponse::end(); + } + + create_junction(args.coords); + create_attached(); + + return ToolResponse(); + } + + void ToolPlaceJunction::create_junction(const Coordi &c) { + temp = core.r->insert_junction(UUID::random()); + temp->temp = true; + temp->position = c; + } + + ToolResponse ToolPlaceJunction::update(const ToolArgs &args) { + if(update_attached(args)) { + return ToolResponse(); + } + + if(args.type == ToolEventType::MOVE) { + temp->position = args.coords; + } + else if(args.type == ToolEventType::CLICK) { + if(args.button == 1) { + if(core.c) { + for(auto it: core.c->get_net_lines()) { + if(it->coord_on_line(temp->position)) { + std::cout << "on line" << std::endl; + if(!check_line(it)) + return ToolResponse(); + + core.c->get_sheet()->split_line_net(it, temp); + break; + } + } + } + temp->temp = false; + junctions_placed.push_front(temp); + + create_junction(args.coords); + create_attached(); + } + else if(args.button == 3) { + delete_attached(); + core.r->delete_junction(temp->uuid); + temp = 0; + core.r->commit(); + core.r->selection.clear(); + for(auto it: junctions_placed) { + core.r->selection.emplace(it->uuid, ObjectType::JUNCTION); + } + return ToolResponse::end(); + } + } + else if(args.type == ToolEventType::KEY) { + if(args.key == GDK_KEY_Escape) { + core.r->revert(); + return ToolResponse::end(); + } + } + return ToolResponse(); + } + +} diff --git a/core/tool_place_junction.hpp b/core/tool_place_junction.hpp new file mode 100644 index 000000000..a3795639c --- /dev/null +++ b/core/tool_place_junction.hpp @@ -0,0 +1,28 @@ +#pragma once +#include "core.hpp" +#include + +namespace horizon { + + class ToolPlaceJunction : public ToolBase { + public : + ToolPlaceJunction(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override ; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + + protected: + Junction *temp = 0; + std::forward_list junctions_placed; + + + void create_junction(const Coordi &c); + virtual void create_attached() {} + virtual void delete_attached() {} + virtual bool update_attached(const ToolArgs &args) {return false;} + virtual bool check_line(class LineNet *li) {return true;} + virtual bool begin_attached() {return true;} + + + }; +} diff --git a/core/tool_place_net_label.cpp b/core/tool_place_net_label.cpp new file mode 100644 index 000000000..63bbeacb9 --- /dev/null +++ b/core/tool_place_net_label.cpp @@ -0,0 +1,118 @@ +#include "tool_place_net_label.hpp" +#include +#include "core_schematic.hpp" + +namespace horizon { + + ToolPlaceNetLabel::ToolPlaceNetLabel(Core *c, ToolID tid):ToolPlaceJunction(c, tid) { + name = "Place Net label"; + } + + bool ToolPlaceNetLabel::can_begin() { + return core.c; + } + + void ToolPlaceNetLabel::create_attached() { + auto uu = UUID::random(); + la = &core.c->get_sheet()->net_labels.emplace(uu, uu).first->second; + la->orientation = last_orientation; + la->junction = temp; + } + + void ToolPlaceNetLabel::delete_attached() { + if(la) { + core.c->get_sheet()->net_labels.erase(la->uuid); + la = nullptr; + } + } + + static Orientation transform_orienation(Orientation orientation, bool rotate) { + Orientation new_orientation; + if(rotate) { + switch(orientation) { + case Orientation::UP: new_orientation=Orientation::RIGHT; break; + case Orientation::DOWN: new_orientation=Orientation::LEFT; break; + case Orientation::LEFT: new_orientation=Orientation::UP; break; + case Orientation::RIGHT: new_orientation=Orientation::DOWN; break; + } + } + else { + switch(orientation) { + case Orientation::UP: new_orientation=Orientation::UP; break; + case Orientation::DOWN: new_orientation=Orientation::DOWN; break; + case Orientation::LEFT: new_orientation=Orientation::RIGHT; break; + case Orientation::RIGHT: new_orientation=Orientation::LEFT; break; + } + } + return new_orientation; + } + + bool ToolPlaceNetLabel::check_line(LineNet *li) { + if(li->bus) + return false; + return true; + } + + bool ToolPlaceNetLabel::update_attached(const ToolArgs &args) { + if(args.type == ToolEventType::CLICK) { + if(args.button == 1) { + if(args.target.type == ObjectType::JUNCTION) { + Junction *j = core.r->get_junction(args.target.path.at(0)); + if(j->bus) + return true; + la->junction = j; + create_attached(); + return true; + } + else if(args.target.type == ObjectType::SYMBOL_PIN) { + SchematicSymbol *schsym = core.c->get_schematic_symbol(args.target.path.at(0)); + SymbolPin *pin = &schsym->symbol.pins.at(args.target.path.at(1)); + UUIDPath<2> connpath(schsym->gate->uuid, args.target.path.at(1)); + if(schsym->component->connections.count(connpath) == 0) { + //sympin not connected + auto uu = UUID::random(); + auto *line = &core.c->get_sheet()->net_lines.emplace(uu, uu).first->second; + line->net = core.c->get_schematic()->block->insert_net(); + line->from.connect(schsym, pin); + line->to.connect(temp); + schsym->component->connections.emplace(UUIDPath<2>(schsym->gate->uuid, pin->uuid), line->net); + + temp->temp = false; + temp->net = line->net; + switch(la->orientation) { + case Orientation::LEFT: temp->position.x -= 1.25_mm; break; + case Orientation::RIGHT: temp->position.x += 1.25_mm; break; + case Orientation::DOWN: temp->position.y -= 1.25_mm; break; + case Orientation::UP: temp->position.y += 1.25_mm; break; + } + create_junction(args.coords); + create_attached(); + } + + return true; + } + } + } + else if(args.type == ToolEventType::KEY) { + if(la) { + if(args.key == GDK_KEY_r) { + la->orientation = transform_orienation(la->orientation, true); + last_orientation = la->orientation; + return true; + } + else if(args.key == GDK_KEY_plus || args.key == GDK_KEY_equal) { + la->size += 0.5_mm; + return true; + } + else if(args.key == GDK_KEY_minus ) { + if(la->size > 0.5_mm) { + la->size -= 0.5_mm; + } + return true; + } + + } + } + return false; + } +} diff --git a/core/tool_place_net_label.hpp b/core/tool_place_net_label.hpp new file mode 100644 index 000000000..90398e3b4 --- /dev/null +++ b/core/tool_place_net_label.hpp @@ -0,0 +1,24 @@ +#pragma once +#include "core.hpp" +#include "tool_place_junction.hpp" +#include "net_label.hpp" +#include + +namespace horizon { + + class ToolPlaceNetLabel : public ToolPlaceJunction { + public : + ToolPlaceNetLabel(Core *c, ToolID tid); + bool can_begin() override; + + protected: + std::forward_list labels_placed; + void create_attached(); + void delete_attached(); + bool update_attached(const ToolArgs &args); + bool check_line(LineNet *li); + NetLabel *la = nullptr; + Orientation last_orientation = Orientation::RIGHT; + + }; +} diff --git a/core/tool_place_pad.cpp b/core/tool_place_pad.cpp new file mode 100644 index 000000000..5aea7a6d0 --- /dev/null +++ b/core/tool_place_pad.cpp @@ -0,0 +1,64 @@ +#include "tool_place_pad.hpp" +#include +#include "core_package.hpp" + +namespace horizon { + + ToolPlacePad::ToolPlacePad(Core *c, ToolID tid): ToolBase(c, tid) { + name = "Place pad"; + } + + bool ToolPlacePad::can_begin() { + return core.k; + } + + ToolResponse ToolPlacePad::begin(const ToolArgs &args) { + std::cout << "tool add comp\n"; + bool r; + UUID padstack_uuid; + std::tie(r, padstack_uuid) = core.r->dialogs.select_padstack(core.k->get_package()->uuid); + if(!r) { + return ToolResponse::end(); + } + + padstack = core.r->m_pool->get_padstack(padstack_uuid); + create_pad(args.coords); + return ToolResponse(); + } + + void ToolPlacePad::create_pad(const Coordi &pos) { + Package *pkg = core.k->get_package(); + auto uu = UUID::random(); + temp = &pkg->pads.emplace(uu, Pad(uu, padstack)).first->second; + temp->placement.shift = pos; + } + + ToolResponse ToolPlacePad::update(const ToolArgs &args) { + + if(args.type == ToolEventType::MOVE) { + temp->placement.shift = args.coords; + } + else if(args.type == ToolEventType::CLICK) { + if(args.button == 1) { + + + create_pad(args.coords); + } + else if(args.button == 3) { + core.k->get_package()->pads.erase(temp->uuid); + temp = 0; + core.r->commit(); + core.r->selection.clear(); + return ToolResponse::end(); + } + } + else if(args.type == ToolEventType::KEY) { + if(args.key == GDK_KEY_Escape) { + core.r->revert(); + return ToolResponse::end(); + } + } + return ToolResponse(); + } + +} diff --git a/core/tool_place_pad.hpp b/core/tool_place_pad.hpp new file mode 100644 index 000000000..e568c5798 --- /dev/null +++ b/core/tool_place_pad.hpp @@ -0,0 +1,18 @@ +#pragma once +#include "core.hpp" +#include "pad.hpp" + +namespace horizon { + class ToolPlacePad : public ToolBase { + public : + ToolPlacePad(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override ; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + + private : + Padstack *padstack = nullptr; + Pad *temp = nullptr; + void create_pad(const Coordi &c); + }; +} diff --git a/core/tool_place_power_symbol.cpp b/core/tool_place_power_symbol.cpp new file mode 100644 index 000000000..1ade4f880 --- /dev/null +++ b/core/tool_place_power_symbol.cpp @@ -0,0 +1,115 @@ +#include "tool_place_power_symbol.hpp" +#include +#include "core_schematic.hpp" + +namespace horizon { + + ToolPlacePowerSymbol::ToolPlacePowerSymbol(Core *c, ToolID tid): ToolPlaceJunction(c, tid) { + name = "Place Power Symbol"; + } + + bool ToolPlacePowerSymbol::can_begin() { + return core.c; + } + + bool ToolPlacePowerSymbol::begin_attached() { + bool r; + UUID net_uuid; + std::tie(r, net_uuid) = core.r->dialogs.select_net(true); + if(!r) { + return false; + } + net = &core.c->get_schematic()->block->nets.at(net_uuid); + return true; + } + + void ToolPlacePowerSymbol::create_attached() { + auto uu = UUID::random(); + sym = &core.c->get_sheet()->power_symbols.emplace(uu, uu).first->second; + sym->net = net; + sym->junction = temp; + temp->net = net; + } + + void ToolPlacePowerSymbol::delete_attached() { + if(sym) { + core.c->get_sheet()->power_symbols.erase(sym->uuid); + temp->net = nullptr; + } + } + + bool ToolPlacePowerSymbol::do_merge(Net *other) { + if(!other) + return true; + if(other->is_bussed) + return false; //can't merge with bussed net + if(other->is_power && other != net) { + //junction is connected to other power net, can't merge + return false; + } + else if(!other->is_power && other != net) { + core.c->get_schematic()->block->merge_nets(other, net); + core.c->get_schematic()->expand(); + return true; + } + return false; + + } + + bool ToolPlacePowerSymbol::check_line(LineNet *li) { + if(li->bus) + return false; + + return do_merge(li->net); + } + + bool ToolPlacePowerSymbol::update_attached(const ToolArgs &args) { + if(args.type == ToolEventType::CLICK) { + if(args.button == 1) { + if(args.target.type == ObjectType::JUNCTION) { + Junction *j = core.r->get_junction(args.target.path.at(0)); + if(j->bus) + return true; + bool merged = do_merge(j->net); + if(!merged) { + return true; + } + if(!j->net) { + j->net = net; + } + sym->junction = j; + create_attached(); + return true; + } + if(args.target.type == ObjectType::SYMBOL_PIN) { + SchematicSymbol *schsym = core.c->get_schematic_symbol(args.target.path.at(0)); + SymbolPin *pin = &schsym->symbol.pins.at(args.target.path.at(1)); + UUIDPath<2> connpath(schsym->gate->uuid, args.target.path.at(1)); + if(schsym->component->connections.count(connpath) == 0) { + //sympin not connected + auto uu = UUID::random(); + auto *line = &core.c->get_sheet()->net_lines.emplace(uu, uu).first->second; + line->net = net; + line->from.connect(schsym, pin); + line->to.connect(temp); + schsym->component->connections.emplace(UUIDPath<2>(schsym->gate->uuid, pin->uuid), net); + + temp->temp = false; + temp->position.y -= 1.25_mm; + create_junction(args.coords); + create_attached(); + } + + return true; + } + } + } + else if(args.type == ToolEventType::KEY) { + if(args.key == GDK_KEY_e) { + sym->mirror ^= true; + } + } + + return false; + } +} diff --git a/core/tool_place_power_symbol.hpp b/core/tool_place_power_symbol.hpp new file mode 100644 index 000000000..7b2bfc75a --- /dev/null +++ b/core/tool_place_power_symbol.hpp @@ -0,0 +1,28 @@ +#pragma once +#include "core.hpp" +#include "tool_place_junction.hpp" +#include "power_symbol.hpp" +#include + +namespace horizon { + + class ToolPlacePowerSymbol : public ToolPlaceJunction { + public : + ToolPlacePowerSymbol(Core *c, ToolID tid); + bool can_begin() override; + + protected: + void create_attached(); + void delete_attached(); + bool begin_attached(); + bool update_attached(const ToolArgs &args); + bool check_line(LineNet *li); + PowerSymbol *sym = nullptr; + std::forward_list symbols_placed; + Net *net = nullptr; + + private: + bool do_merge(Net *other); + + }; +} diff --git a/core/tool_place_text.cpp b/core/tool_place_text.cpp new file mode 100644 index 000000000..0b48f77b6 --- /dev/null +++ b/core/tool_place_text.cpp @@ -0,0 +1,53 @@ +#include "tool_place_text.hpp" +#include + +namespace horizon { + + ToolPlaceText::ToolPlaceText(Core *c, ToolID tid): ToolBase(c, tid) { + name = "Place Text"; + } + + bool ToolPlaceText::can_begin() { + return core.r->has_object_type(ObjectType::TEXT); + } + + ToolResponse ToolPlaceText::begin(const ToolArgs &args) { + std::cout << "tool place text\n"; + + temp = core.r->insert_text(UUID::random()); + temp->text = "TEXT"; + temp->position = args.coords; + + return ToolResponse(); + } + ToolResponse ToolPlaceText::update(const ToolArgs &args) { + if(args.type == ToolEventType::MOVE) { + temp->position = args.coords; + } + else if(args.type == ToolEventType::CLICK) { + if(args.button == 1) { + texts_placed.push_front(temp); + temp = core.r->insert_text(UUID::random()); + temp->text = "TEXT"; + temp->position = args.coords; + } + else if(args.button == 3) { + core.r->delete_text(temp->uuid); + core.r->selection.clear(); + for(auto it: texts_placed) { + core.r->selection.emplace(it->uuid, ObjectType::TEXT); + } + core.r->commit(); + return ToolResponse::end(); + } + } + else if(args.type == ToolEventType::KEY) { + if(args.key == GDK_KEY_Escape) { + core.r->revert(); + return ToolResponse::end(); + } + } + return ToolResponse(); + } + +} diff --git a/core/tool_place_text.hpp b/core/tool_place_text.hpp new file mode 100644 index 000000000..7e49a28f3 --- /dev/null +++ b/core/tool_place_text.hpp @@ -0,0 +1,20 @@ +#pragma once +#include "core.hpp" +#include + +namespace horizon { + + class ToolPlaceText: public ToolBase { + public : + ToolPlaceText(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override ; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + + private: + Text *temp = 0; + std::forward_list texts_placed; + + + }; +} diff --git a/core/tool_place_via.cpp b/core/tool_place_via.cpp new file mode 100644 index 000000000..113f5a9ce --- /dev/null +++ b/core/tool_place_via.cpp @@ -0,0 +1,51 @@ +#include "tool_place_via.hpp" +#include +#include "core_board.hpp" + +namespace horizon { + + ToolPlaceVia::ToolPlaceVia(Core *c, ToolID tid): ToolPlaceJunction(c, tid) { + name = "Place Via"; + } + + bool ToolPlaceVia::can_begin() { + return core.b; + } + + bool ToolPlaceVia::begin_attached() { + bool r; + UUID padstack_uuid; + std::tie(r, padstack_uuid) = core.r->dialogs.select_via_padstack(core.b->get_via_padstack_provider()); + if(!r) { + return false; + } + padstack = core.b->get_via_padstack_provider()->get_padstack(padstack_uuid); + return true; + } + + void ToolPlaceVia::create_attached() { + auto uu = UUID::random(); + via = &core.b->get_board()->vias.emplace(uu, Via(uu, padstack)).first->second; + via->junction = temp; + } + + void ToolPlaceVia::delete_attached() { + if(via) { + core.b->get_board()->vias.erase(via->uuid); + } + } + + bool ToolPlaceVia::update_attached(const ToolArgs &args) { + if(args.type == ToolEventType::CLICK) { + if(args.button == 1) { + if(args.target.type == ObjectType::JUNCTION) { + Junction *j = core.r->get_junction(args.target.path.at(0)); + via->junction = j; + create_attached(); + return true; + } + } + } + return false; + } +} diff --git a/core/tool_place_via.hpp b/core/tool_place_via.hpp new file mode 100644 index 000000000..a562c7ba7 --- /dev/null +++ b/core/tool_place_via.hpp @@ -0,0 +1,26 @@ +#pragma once +#include "core.hpp" +#include "tool_place_junction.hpp" +#include "via.hpp" +#include + +namespace horizon { + + class ToolPlaceVia: public ToolPlaceJunction { + public : + ToolPlaceVia(Core *c, ToolID tid); + bool can_begin() override; + + protected: + void create_attached(); + void delete_attached(); + bool begin_attached(); + bool update_attached(const ToolArgs &args); + Via *via = nullptr; + Padstack *padstack = nullptr; + std::forward_list vias_placed; + + private: + + }; +} diff --git a/core/tool_route_track.cpp b/core/tool_route_track.cpp new file mode 100644 index 000000000..a0782fcb3 --- /dev/null +++ b/core/tool_route_track.cpp @@ -0,0 +1,402 @@ +#include "tool_route_track.hpp" +#include +#include "core_board.hpp" +#include "obstacle/canvas_obstacle.hpp" + +namespace horizon { + + ToolRouteTrack::ToolRouteTrack(Core *c, ToolID tid):ToolBase(c, tid) { + name = "Route Track"; + } + + bool ToolRouteTrack::can_begin() { + return core.b; + } + + ToolResponse ToolRouteTrack::begin(const ToolArgs &args) { + std::cout << "tool route track\n"; + core.r->selection.clear(); + return ToolResponse(); + } + + void ToolRouteTrack::begin_track(const ToolArgs &args) { + auto c = args.target.p; + CanvasObstacle ca; + ca.routing_layer = args.work_layer; + routing_layer = args.work_layer; + ca.routing_width = net->net_class->default_width; + ca.routing_net = net; + ca.set_core(core.r); + ca.update(*core.b->get_board()); + obstacles.clear(); + ca.clipper.Execute(ClipperLib::ctUnion, obstacles, ClipperLib::pftNonZero, ClipperLib::pftNonZero); + core.b->get_board()->obstacles = obstacles; + track_path.clear(); + track_path.emplace_back(c.x, c.y); + track_path.emplace_back(c.x, c.y); + track_path.emplace_back(c.x, c.y); + track_path_known_good = track_path; + } + + void ToolRouteTrack::update_track(const Coordi &c) { + assert(track_path.size()>=3); + auto l = track_path.end()-1; + l->X = c.x; + l->Y = c.y; + + auto k = l-1; + auto b = l-2; + auto dx = std::abs(l->X-b->X); + auto dy = std::abs(l->Y-b->Y); + if(dy > dx) { + auto si = (l->YY)?1:-1; + if(bend_mode) { + k->X = b->X; + k->Y = l->Y + si*dx; + } + else { + k->X = l->X; + k->Y = b->Y - si*dx; + } + } + else { + auto si = (l->XX)?1:-1; + if(bend_mode) { + k->Y = b->Y; + k->X = l->X + si*dy; + } + else { + k->Y = l->Y; + k->X = b->X - si*dy; + } + } + + core.b->get_board()->track_path = track_path; + } + + bool ToolRouteTrack::check_track_path(const ClipperLib::Path &p) { + ClipperLib::Clipper clipper; + clipper.AddPaths(obstacles, ClipperLib::ptClip, true); + clipper.AddPath(p, ClipperLib::ptSubject, false); + ClipperLib::PolyTree solution; + clipper.Execute(ClipperLib::ctIntersection, solution, ClipperLib::pftNonZero, ClipperLib::pftNonZero); + return solution.ChildCount() ==0; + } + + static Coordi coordi_fron_intpt(const ClipperLib::IntPoint &p) { + Coordi r(p.X, p.Y); + return r; + } + + void ToolRouteTrack::update_temp_track() { + auto brd = core.b->get_board(); + for(auto &it: temp_tracks) { + brd->tracks.erase(it->uuid); + } + temp_tracks.clear(); + for(auto &it: temp_junctions) { + brd->junctions.erase(it->uuid); + } + temp_junctions.clear(); + if(track_path.size()>=3) { + + ClipperLib::Path path_simp; + path_simp.push_back(track_path.at(0)); + path_simp.push_back(track_path.at(1)); + path_simp.push_back(track_path.at(2)); + + for(auto it = track_path.cbegin()+3; it < track_path.cend(); it++) { + if(path_simp.back() == *it) + continue; + path_simp.push_back(*it); + auto p0 = coordi_fron_intpt(*(path_simp.end()-1)); + auto p1 = coordi_fron_intpt(*(path_simp.end()-2)); + auto p2 = coordi_fron_intpt(*(path_simp.end()-3)); + + auto va = p2-p1; + auto vb = p1-p0; + + if((va.dot(vb))*(va.dot(vb)) == va.mag_sq()*vb.mag_sq()) { + path_simp.erase(path_simp.end()-2); + } + + } + + + + /*auto tj = core.r->insert_junction(UUID::random()); + tj->temp = true; + tj->net = net; + tj->net_segment = net_segment; + tj->position = coordi_fron_intpt(path_simp.at(1)); + temp_junctions.push_back(tj); + + auto uu = UUID::random(); + auto tt = &brd->tracks.emplace(uu, uu).first->second; + tt->from = conn_start; + tt->to.connect(tj); + tt->net = net; + tt->net_segment = net_segment; + tt->layer = routing_layer; + temp_tracks.push_back(tt);*/ + + + Junction *tj = nullptr; + for(auto it = path_simp.cbegin()+1; it < path_simp.cend(); it++) { + if(*it != *(it-1)) { + auto tuu = UUID::random(); + auto tr = &brd->tracks.emplace(tuu, tuu).first->second; + tr->net = net; + tr->net_segment = net_segment; + tr->layer = routing_layer; + temp_tracks.push_back(tr); + + auto ju = core.r->insert_junction(UUID::random()); + ju->temp = true; + ju->position = coordi_fron_intpt(*it); + ju->net = net; + ju->net_segment = net_segment; + temp_junctions.push_back(ju); + + if(tj == nullptr) { + tr->from = conn_start; + } + else { + tr->from.connect(tj); + } + + tr->to.connect(ju); + tj = ju; + } + } + + + } + if(via) { + via->junction = temp_junctions.back(); + } + + core.b->get_board()->update_airwires(); + } + + bool ToolRouteTrack::try_move_track(const ToolArgs &args) { + bool drc_okay = false; + update_track(args.coords); + if(!check_track_path(track_path)) { //drc error + std::cout << "drc error" << std::endl; + bend_mode ^= true; + update_track(args.coords); //flip + if(!check_track_path(track_path)) { //still drc errror + std::cout << "still drc error" << std::endl; + bend_mode ^= true; + track_path = track_path_known_good; + assert(check_track_path(track_path)); + core.b->get_board()->track_path = track_path; + + //create new segment + //track_path.emplace_back(args.coords.x, args.coords.y); + //track_path.emplace_back(args.coords.x, args.coords.y); + } + else { + std::cout << "no drc error after flip" << std::endl; + track_path_known_good = track_path; + drc_okay = true; + } + } + else { + std::cout << "no drc error" << std::endl; + track_path_known_good = track_path; + drc_okay = true; + } + update_temp_track(); + return drc_okay; + } + + ToolResponse ToolRouteTrack::update(const ToolArgs &args) { + if(args.type == ToolEventType::KEY) { + if(args.key == GDK_KEY_Escape) { + core.b->revert(); + core.b->get_board()->obstacles.clear(); + core.b->get_board()->track_path.clear(); + return ToolResponse::end(); + } + } + if(net == nullptr) { //begin route + if(args.type == ToolEventType::CLICK) { + if(args.target.type == ObjectType::PAD) { + auto pkg = &core.b->get_board()->packages.at(args.target.path.at(0)); + auto pad = &pkg->package.pads.at(args.target.path.at(1)); + net = pad->net; + net_segment = pad->net_segment; + if(net) { + ToolArgs a(args); + if(!core.b->get_layers().at(a.work_layer).copper) { + a.work_layer = 0; + } + if((pad->padstack.type == Padstack::Type::TOP) ^ pkg->flip) { + a.work_layer = 0; //top + } + else if((pad->padstack.type == Padstack::Type::BOTTOM) ^ pkg->flip) { + a.work_layer = -100; + } + else if(pad->padstack.type == Padstack::Type::THROUGH) { + //it's okay + } + + + begin_track(a); + conn_start.connect(pkg, pad); + std::cout << "begin net" << std::endl; + + return ToolResponse::change_layer(a.work_layer); + } + } + else if(args.target.type == ObjectType::JUNCTION) { + auto junc = core.r->get_junction(args.target.path.at(0)); + net = junc->net; + net_segment = junc->net_segment; + if(net) { + ToolArgs a(args); + if(!core.b->get_layers().at(a.work_layer).copper) { + a.work_layer = 0; + } + if(!junc->has_via) { + if(junc->layer<10000) { + a.work_layer = junc->layer; + } + } + begin_track(a); + conn_start.connect(junc); + std::cout << "begin net" << std::endl; + return ToolResponse::change_layer(a.work_layer); + } + } + } + } + else { + if(args.type == ToolEventType::MOVE) { + try_move_track(args); + std::cout << "temp track" << std::endl; + for(const auto &it: track_path) { + std::cout << it.X << " " << it.Y << std::endl; + } + std::cout << std::endl << std::endl; + } + else if(args.type == ToolEventType::CLICK) { + if(args.button == 1) { + if(!args.target.is_valid()) { + track_path.emplace_back(args.coords.x, args.coords.y); + track_path.emplace_back(args.coords.x, args.coords.y); + bend_mode ^= true; + try_move_track(args); + } + else { + if(!try_move_track(args)) //drc not okay + return ToolResponse(); + if(args.target.type == ObjectType::PAD) { + auto pkg = &core.b->get_board()->packages.at(args.target.path.at(0)); + auto pad = &pkg->package.pads.at(args.target.path.at(1)); + if(pad->net != net) + return ToolResponse(); + temp_tracks.back()->to.connect(pkg, pad); + } + else if(args.target.type == ObjectType::JUNCTION) { + std::cout << "temp track ju" << std::endl; + for(const auto &it: track_path) { + std::cout << it.X << " " << it.Y << std::endl; + } + + auto junc = core.r->get_junction(args.target.path.at(0)); + if(junc->net && (junc->net != net)) + return ToolResponse(); + temp_tracks.back()->to.connect(junc); + } + else { + return ToolResponse(); + } + + core.r->delete_junction(temp_junctions.back()->uuid); + + core.b->get_board()->track_path.clear(); + core.b->get_board()->obstacles.clear(); + core.b->commit(); + return ToolResponse::end(); + + } + } + else if(args.button == 3) { + auto brd = core.b->get_board(); + + if(track_path.size()>=2 && via == nullptr) { + track_path.pop_back(); + track_path.pop_back(); + update_temp_track(); + } + + core.b->get_board()->track_path.clear(); + core.b->get_board()->obstacles.clear(); + core.b->commit(); + return ToolResponse::end(); + } + } + else if(args.type == ToolEventType::KEY) { + if(args.key == GDK_KEY_slash) { + bend_mode ^= true; + auto &b = track_path.back(); + update_track({b.X, b.Y}); + if(!check_track_path(track_path)) { //drc error + bend_mode ^= true; + update_track({b.X, b.Y}); + } + update_temp_track(); + } + else if(args.key == GDK_KEY_v) { + if(via == nullptr) { + UUID padstack_uuid; + bool r; + std::tie(r, padstack_uuid) = core.r->dialogs.select_via_padstack(core.b->get_via_padstack_provider()); + if(r) { + auto uu = UUID::random(); + via = &core.b->get_board()->vias.emplace(uu, Via(uu, core.b->get_via_padstack_provider()->get_padstack(padstack_uuid))).first->second; + update_temp_track(); + } + } + else { + core.b->get_board()->vias.erase(via->uuid); + via = nullptr; + } + } + else if(args.key == GDK_KEY_BackSpace) { + if(track_path.size()>3) { + track_path.pop_back(); + track_path.pop_back(); + update_track(args.coords); + if(!check_track_path(track_path)) { //drc error + std::cout << "drc error" << std::endl; + bend_mode ^= true; + update_track(args.coords); //flip + if(!check_track_path(track_path)) { //still drc errror + std::cout << "still drc error" << std::endl; + bend_mode ^= true; + track_path = track_path_known_good; + assert(check_track_path(track_path)); + core.b->get_board()->track_path = track_path; + } + else { + std::cout << "no drc error after flip" << std::endl; + track_path_known_good = track_path; + } + } + else { + std::cout << "no drc error" << std::endl; + track_path_known_good = track_path; + } + update_temp_track(); + } + } + } + } + return ToolResponse(); + } + +} diff --git a/core/tool_route_track.hpp b/core/tool_route_track.hpp new file mode 100644 index 000000000..d3dddcc30 --- /dev/null +++ b/core/tool_route_track.hpp @@ -0,0 +1,38 @@ +#pragma once +#include "hole.hpp" +#include "core.hpp" +#include "clipper/clipper.hpp" +#include +#include "track.hpp" +#include "via.hpp" + +namespace horizon { + + class ToolRouteTrack: public ToolBase { + public : + ToolRouteTrack (Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override ; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + + private: + Net *net = nullptr; + UUID net_segment; + int routing_layer; + void begin_track(const ToolArgs &args); + bool try_move_track(const ToolArgs &args); + void update_track(const Coordi &c); + bool check_track_path(const ClipperLib::Path &p); + void update_temp_track(); + bool bend_mode = false; + ClipperLib::Paths obstacles; + ClipperLib::Path track_path; + ClipperLib::Path track_path_known_good; + Track::Connection conn_start; + Track::Connection conn_end; + std::deque temp_junctions; + std::deque temp_tracks; + + Via *via = nullptr; + }; +} diff --git a/core/tool_smash.cpp b/core/tool_smash.cpp new file mode 100644 index 000000000..8afd0128d --- /dev/null +++ b/core/tool_smash.cpp @@ -0,0 +1,36 @@ +#include "tool_smash.hpp" +#include +#include "core_schematic.hpp" + +namespace horizon { + + ToolSmash::ToolSmash(Core *c, ToolID tid): ToolBase(c, tid) { + } + + bool ToolSmash::can_begin() { + for(const auto &it : core.r->selection) { + if(it.type == ObjectType::SCHEMATIC_SYMBOL) { + return true; + } + } + return false; + } + + ToolResponse ToolSmash::begin(const ToolArgs &args) { + std::cout << "tool smash\n"; + for(const auto &it : args.selection) { + if(it.type == ObjectType::SCHEMATIC_SYMBOL) { + if(tool_id == ToolID::SMASH) + core.c->get_schematic()->smash_symbol(core.c->get_sheet(), core.c->get_schematic_symbol(it.uuid)); + else + core.c->get_schematic()->unsmash_symbol(core.c->get_sheet(), core.c->get_schematic_symbol(it.uuid)); + } + } + core.c->commit(); + return ToolResponse::end(); + } + ToolResponse ToolSmash::update(const ToolArgs &args) { + return ToolResponse(); + } + +} diff --git a/core/tool_smash.hpp b/core/tool_smash.hpp new file mode 100644 index 000000000..e960bbe89 --- /dev/null +++ b/core/tool_smash.hpp @@ -0,0 +1,17 @@ +#pragma once +#include "core.hpp" +#include + +namespace horizon { + + class ToolSmash : public ToolBase { + public : + ToolSmash(Core *c, ToolID tid); + ToolResponse begin(const ToolArgs &args) override; + ToolResponse update(const ToolArgs &args) override; + bool can_begin() override; + + private: + + }; +} diff --git a/dialogs/annotate.cpp b/dialogs/annotate.cpp new file mode 100644 index 000000000..b023b687e --- /dev/null +++ b/dialogs/annotate.cpp @@ -0,0 +1,116 @@ +#include "annotate.hpp" +#include "widgets/net_button.hpp" +#include +#include +#include + +namespace horizon { + + + class GangedSwitch: public Gtk::Box { + public: + GangedSwitch(); + void append(const std::string &id, const std::string &label); + }; + + GangedSwitch::GangedSwitch(): Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0) { + get_style_context()->add_class("linked"); + } + + void GangedSwitch::append(const std::string &id, const std::string &label) { + auto rb = Gtk::manage(new Gtk::RadioButton(label)); + rb->set_mode(false); + auto ch = get_children(); + if(ch.size()>0) { + rb->join_group(dynamic_cast(*ch.front())); + } + rb->show(); + pack_start(*rb, false, false, 0); + + } + + AnnotateDialog::AnnotateDialog(Gtk::Window *parent, CoreSchematic *c) : + Gtk::Dialog("Annotation", *parent, Gtk::DialogFlags::DIALOG_MODAL|Gtk::DialogFlags::DIALOG_USE_HEADER_BAR), + core(c) + { + add_button("Cancel", Gtk::ResponseType::RESPONSE_CANCEL); + auto ok_button = add_button("Annotate", Gtk::ResponseType::RESPONSE_OK); + ok_button->signal_clicked().connect(sigc::mem_fun(this, &AnnotateDialog::ok_clicked)); + set_default_response(Gtk::ResponseType::RESPONSE_OK); + //set_default_size(400, 300); + + auto grid = Gtk::manage(new Gtk::Grid()); + grid->set_column_spacing(10); + grid->set_row_spacing(10); + grid->set_margin_bottom(20); + grid->set_margin_top(20); + grid->set_margin_end(20); + grid->set_margin_start(20); + grid->set_halign(Gtk::ALIGN_CENTER); + + int top = 0; + auto sch = core->get_schematic(); + { + auto la = Gtk::manage(new Gtk::Label("Order")); + la->set_halign(Gtk::ALIGN_END); + la->get_style_context()->add_class("dim-label"); + w_order = Gtk::manage(new Gtk::ComboBoxText()); + w_order->append(std::to_string(static_cast(Schematic::Annotation::Order::RIGHT_DOWN)), "Right-Down"); + w_order->append(std::to_string(static_cast(Schematic::Annotation::Order::DOWN_RIGHT)), "Down-Right"); + w_order->set_active_id(std::to_string(static_cast(sch->annotation.order))); + grid->attach(*la, 0, top, 1, 1); + grid->attach(*w_order, 1, top, 1, 1); + top++; + } + { + auto la = Gtk::manage(new Gtk::Label("Mode")); + la->set_halign(Gtk::ALIGN_END); + la->get_style_context()->add_class("dim-label"); + w_mode = Gtk::manage(new Gtk::ComboBoxText()); + w_mode->append(std::to_string(static_cast(Schematic::Annotation::Mode::SEQUENTIAL)), "Sequential"); + w_mode->append(std::to_string(static_cast(Schematic::Annotation::Mode::SHEET_100)), "Sheet increment 100"); + w_mode->append(std::to_string(static_cast(Schematic::Annotation::Mode::SHEET_1000)), "Sheet increment 1000"); + w_mode->set_active_id(std::to_string(static_cast(sch->annotation.mode))); + grid->attach(*la, 0, top, 1, 1); + grid->attach(*w_mode, 1, top, 1, 1); + top++; + } + { + auto la = Gtk::manage(new Gtk::Label("Keep existing")); + la->set_halign(Gtk::ALIGN_END); + la->get_style_context()->add_class("dim-label"); + w_keep = Gtk::manage(new Gtk::Switch()); + w_keep->set_halign(Gtk::ALIGN_START); + w_keep->set_active(sch->annotation.keep); + grid->attach(*la, 0, top, 1, 1); + grid->attach(*w_keep, 1, top, 1, 1); + top++; + } + { + auto la = Gtk::manage(new Gtk::Label("Fill gaps")); + la->set_halign(Gtk::ALIGN_END); + la->get_style_context()->add_class("dim-label"); + w_fill_gaps = Gtk::manage(new Gtk::Switch()); + w_fill_gaps->set_halign(Gtk::ALIGN_START); + w_fill_gaps->set_active(sch->annotation.fill_gaps); + grid->attach(*la, 0, top, 1, 1); + grid->attach(*w_fill_gaps, 1, top, 1, 1); + top++; + } + + + + get_content_area()->pack_start(*grid, true, true, 0); + + show_all(); + } + + void AnnotateDialog::ok_clicked() { + auto sch = core->get_schematic(); + sch->annotation.order = static_cast(std::stoi(w_order->get_active_id())); + sch->annotation.mode = static_cast(std::stoi(w_mode->get_active_id())); + sch->annotation.fill_gaps = w_fill_gaps->get_active(); + sch->annotation.keep = w_keep->get_active(); + sch->annotate(); + } +} diff --git a/dialogs/annotate.hpp b/dialogs/annotate.hpp new file mode 100644 index 000000000..575283825 --- /dev/null +++ b/dialogs/annotate.hpp @@ -0,0 +1,26 @@ +#pragma once +#include +#include +#include +#include "common.hpp" +#include "uuid.hpp" +#include "core/core_schematic.hpp" +namespace horizon { + + + class AnnotateDialog: public Gtk::Dialog { + public: + AnnotateDialog(Gtk::Window *parent, CoreSchematic *c); + bool valid = false; + + + private : + CoreSchematic *core = nullptr; + Gtk::Switch *w_fill_gaps = nullptr; + Gtk::Switch *w_keep = nullptr; + Gtk::ComboBoxText *w_order = nullptr; + Gtk::ComboBoxText *w_mode= nullptr; + + void ok_clicked(); + }; +} diff --git a/dialogs/ask_datum.cpp b/dialogs/ask_datum.cpp new file mode 100644 index 000000000..7a263e648 --- /dev/null +++ b/dialogs/ask_datum.cpp @@ -0,0 +1,56 @@ +#include "ask_datum.hpp" +#include "widgets/spin_button_dim.hpp" +#include + +namespace horizon { + + + AskDatumDialog::AskDatumDialog(Gtk::Window *parent, const std::string &label, bool mode_xy ) : + Gtk::Dialog("Enter Datum", *parent, Gtk::DialogFlags::DIALOG_MODAL|Gtk::DialogFlags::DIALOG_USE_HEADER_BAR) + { + add_button("Cancel", Gtk::ResponseType::RESPONSE_CANCEL); + add_button("OK", Gtk::ResponseType::RESPONSE_OK); + + set_default_response(Gtk::ResponseType::RESPONSE_OK); + + + auto box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL, 4)); + auto la = Gtk::manage(new Gtk::Label(label)); + la->set_halign(Gtk::ALIGN_START); + box->pack_start(*la, false, false, 0); + + if(mode_xy == false) { + sp = Gtk::manage(new SpinButtonDim()); + sp->set_margin_start(8); + sp->set_range(0, 1e9); + sp->signal_activate().connect([this]{response(Gtk::RESPONSE_OK);}); + box->pack_start(*sp, false, false, 0); + } + else { + auto xbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4)); + cb_x = Gtk::manage(new Gtk::CheckButton("X")); + cb_x->set_active(true); + sp_x = sp = Gtk::manage(new SpinButtonDim()); + sp_x->set_range(-1e9, 1e9); + sp_x->signal_activate().connect([this]{sp_y->grab_focus();}); + xbox->pack_start(*cb_x, false, false, 0); + xbox->pack_start(*sp_x, true, true, 0); + box->pack_start(*xbox, false, false, 0); + + auto ybox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4)); + cb_y = Gtk::manage(new Gtk::CheckButton("Y")); + cb_y->set_active(true); + sp_y = sp = Gtk::manage(new SpinButtonDim()); + sp_y->set_range(-1e9, 1e9); + sp_y->signal_activate().connect([this]{response(Gtk::RESPONSE_OK);}); + ybox->pack_start(*cb_y, false, false, 0); + ybox->pack_start(*sp_y, true, true, 0); + box->pack_start(*ybox, false, false, 0); + sp_x->grab_focus(); + } + + get_content_area()->pack_start(*box, true, true, 0); + show_all(); + + } +} diff --git a/dialogs/ask_datum.hpp b/dialogs/ask_datum.hpp new file mode 100644 index 000000000..d4bf299c8 --- /dev/null +++ b/dialogs/ask_datum.hpp @@ -0,0 +1,24 @@ +#pragma once +#include +#include +#include +#include "widgets/spin_button_dim.hpp" +namespace horizon { + + + class AskDatumDialog: public Gtk::Dialog { + public: + AskDatumDialog(Gtk::Window *parent, const std::string &label, bool mode_xy = false); + SpinButtonDim *sp = nullptr; + SpinButtonDim *sp_x = nullptr; + SpinButtonDim *sp_y = nullptr; + Gtk::CheckButton *cb_x = nullptr; + Gtk::CheckButton *cb_y = nullptr; + + //virtual ~MainWindow(); + private : + + + + }; +} diff --git a/dialogs/ask_delete_component.cpp b/dialogs/ask_delete_component.cpp new file mode 100644 index 000000000..cb64244b9 --- /dev/null +++ b/dialogs/ask_delete_component.cpp @@ -0,0 +1,25 @@ +#include "ask_delete_component.hpp" +#include + +namespace horizon { + + + AskDeleteComponentDialog::AskDeleteComponentDialog(Gtk::Window *parent, Component *comp) : + Gtk::Dialog("Delete component", *parent, Gtk::DialogFlags::DIALOG_MODAL|Gtk::DialogFlags::DIALOG_USE_HEADER_BAR) + { + add_button("Keep", Gtk::ResponseType::RESPONSE_CANCEL); + auto *del_button = add_button("Delete", Gtk::ResponseType::RESPONSE_OK); + del_button->get_style_context()->add_class("destructive-action"); + set_default_response(Gtk::ResponseType::RESPONSE_CANCEL); + //set_default_size(300, 300); + + auto *la = Gtk::manage(new Gtk::Label("You've unmapped the last gate of "+comp->refdes+".\nDelete Component "+comp->refdes+"?")); + la->set_margin_top(10); + la->set_margin_bottom(10); + + + get_content_area()->pack_start(*la, true, true, 0); + + show_all(); + } +} diff --git a/dialogs/ask_delete_component.hpp b/dialogs/ask_delete_component.hpp new file mode 100644 index 000000000..f846f3419 --- /dev/null +++ b/dialogs/ask_delete_component.hpp @@ -0,0 +1,21 @@ +#pragma once +#include +#include +#include +#include "common.hpp" +#include "uuid.hpp" +#include "component.hpp" +namespace horizon { + + + class AskDeleteComponentDialog: public Gtk::Dialog { + public: + AskDeleteComponentDialog(Gtk::Window *parent, Component *comp); + + //virtual ~MainWindow(); + private : + + + + }; +} diff --git a/dialogs/ask_net_merge.cpp b/dialogs/ask_net_merge.cpp new file mode 100644 index 000000000..de74e0fcb --- /dev/null +++ b/dialogs/ask_net_merge.cpp @@ -0,0 +1,34 @@ +#include "ask_net_merge.hpp" +#include + +namespace horizon { + + void AskNetMergeDialog::ok_clicked() { + + } + + + AskNetMergeDialog::AskNetMergeDialog(Gtk::Window *parent, Net *a, Net *b) : + Gtk::Dialog("Merge nets", *parent, Gtk::DialogFlags::DIALOG_MODAL|Gtk::DialogFlags::DIALOG_USE_HEADER_BAR), + net(a), + into(b){ + add_button("Cancel", Gtk::ResponseType::RESPONSE_CANCEL); + set_default_response(Gtk::ResponseType::RESPONSE_CANCEL); + set_position(Gtk::WIN_POS_MOUSE); + //set_default_size(300, 300); + + + auto *box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL, 4)); + auto *ba = Gtk::manage(new Gtk::Button("Merge \""+net->name+"\" into \""+into->name+"\"")); + box->pack_start(*ba, true, true, 0); + auto *bb = Gtk::manage(new Gtk::Button("Merge \""+into->name+"\" into \""+net->name+"\"")); + box->pack_start(*bb, true, true, 0); + + ba->signal_clicked().connect(sigc::bind(sigc::mem_fun(this, &Gtk::Dialog::response), 1)); + bb->signal_clicked().connect(sigc::bind(sigc::mem_fun(this, &Gtk::Dialog::response), 2)); + + get_content_area()->pack_start(*box, true, true, 0); + + show_all(); + } +} diff --git a/dialogs/ask_net_merge.hpp b/dialogs/ask_net_merge.hpp new file mode 100644 index 000000000..8682b249c --- /dev/null +++ b/dialogs/ask_net_merge.hpp @@ -0,0 +1,23 @@ +#pragma once +#include +#include +#include +#include "common.hpp" +#include "uuid.hpp" +#include "net.hpp" +namespace horizon { + + + class AskNetMergeDialog: public Gtk::Dialog { + public: + AskNetMergeDialog(Gtk::Window *parent, Net *a, Net *b); + + //virtual ~MainWindow(); + private : + Net *net = nullptr; + Net *into = nullptr; + + void ok_clicked(); + + }; +} diff --git a/dialogs/component_pin_names.cpp b/dialogs/component_pin_names.cpp new file mode 100644 index 000000000..716ef0767 --- /dev/null +++ b/dialogs/component_pin_names.cpp @@ -0,0 +1,118 @@ +#include "component_pin_names.hpp" +#include +#include +#include + +namespace horizon { + + + class GatePinEditor: public Gtk::ListBox { + public: + GatePinEditor(Component *c, const Gate *g): + Gtk::ListBox(), + comp(c), gate(g) + { + sg = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL); + set_header_func(sigc::mem_fun(this, &GatePinEditor::header_fun)); + set_selection_mode(Gtk::SELECTION_NONE); + std::deque pins_sorted; + for(const auto &it: gate->unit->pins) { + pins_sorted.push_back(&it.second); + } + std::sort(pins_sorted.begin(), pins_sorted.end(), [](const auto &a, const auto &b) {return a->primary_name < b->primary_name;}); + for(const auto it: pins_sorted) { + auto box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL,16)); + auto la = Gtk::manage(new Gtk::Label(it->primary_name)); + la->set_xalign(0); + sg->add_widget(*la); + box->pack_start(*la, false, false, 0); + + auto combo = Gtk::manage(new Gtk::ComboBoxText()); + + combo->append(std::to_string(-1), it->primary_name); + unsigned int i = 0; + for(const auto &it_pin_name: it->names) { + combo->append(std::to_string(i++), it_pin_name); + } + combo->set_active(0); + auto path = UUIDPath<2>(gate->uuid, it->uuid); + if(comp->pin_names.count(path)) { + combo->set_active(1+comp->pin_names.at(path)); + } + + combo->signal_changed().connect(sigc::bind>( + sigc::mem_fun(this, &GatePinEditor::changed), + combo, path + )); + box->pack_start(*combo, true, true, 0); + + box->set_margin_start(16); + box->set_margin_end(8); + box->set_margin_top(4); + box->set_margin_bottom(4); + + + insert(*box, -1); + } + + } + Component *comp; + const Gate *gate; + + private : + Glib::RefPtr sg; + void header_fun(Gtk::ListBoxRow *row, Gtk::ListBoxRow *before) { + if (before && !row->get_header()) { + auto ret = Gtk::manage(new Gtk::Separator); + row->set_header(*ret); + } + } + + void changed(Gtk::ComboBoxText* combo, UUIDPath<2> path) { + std::cout << "ch " << (std::string) path << combo->get_active_row_number() << std::endl; + comp->pin_names[path] = combo->get_active_row_number()-1; + } + }; + + + + ComponentPinNamesDialog::ComponentPinNamesDialog(Gtk::Window *parent, Component *c) : + Gtk::Dialog("Component "+c->refdes+" pin names", *parent, Gtk::DialogFlags::DIALOG_MODAL|Gtk::DialogFlags::DIALOG_USE_HEADER_BAR), + comp(c) + { + add_button("Cancel", Gtk::ResponseType::RESPONSE_CANCEL); + add_button("OK", Gtk::ResponseType::RESPONSE_OK); + set_default_response(Gtk::ResponseType::RESPONSE_OK); + set_default_size(400, 300); + + auto box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL,0)); + + auto stack = Gtk::manage(new Gtk::Stack()); + stack->set_transition_type(Gtk::STACK_TRANSITION_TYPE_SLIDE_RIGHT); + stack->set_transition_duration(100); + auto sidebar = Gtk::manage(new Gtk::StackSidebar()); + sidebar->set_stack(*stack); + + box->pack_start(*sidebar, false, false, 0); + box->pack_start(*stack, true, true, 0); + + std::deque gates_sorted; + for(const auto &it: comp->entity->gates) { + gates_sorted.push_back(&it.second); + } + std::sort(gates_sorted.begin(), gates_sorted.end(), [](const auto &a, const auto &b) {return a->name < b->name;}); + + + for(auto it: gates_sorted) { + auto ed = Gtk::manage(new GatePinEditor(comp, it)); + auto sc = Gtk::manage(new Gtk::ScrolledWindow()); + sc->add(*ed); + stack->add(*sc, (std::string)it->uuid, "Gate "+it->name); + } + + + get_content_area()->pack_start(*box, true, true, 0); + + show_all(); + } +} diff --git a/dialogs/component_pin_names.hpp b/dialogs/component_pin_names.hpp new file mode 100644 index 000000000..4fa430b66 --- /dev/null +++ b/dialogs/component_pin_names.hpp @@ -0,0 +1,23 @@ +#pragma once +#include +#include +#include +#include "common.hpp" +#include "uuid.hpp" +#include "component.hpp" +namespace horizon { + + + class ComponentPinNamesDialog: public Gtk::Dialog { + public: + ComponentPinNamesDialog(Gtk::Window *parent, Component *c); + bool valid = false; + + + private : + Component *comp = nullptr; + + + void ok_clicked(); + }; +} diff --git a/dialogs/dialogs.cpp b/dialogs/dialogs.cpp new file mode 100644 index 000000000..354c84cf1 --- /dev/null +++ b/dialogs/dialogs.cpp @@ -0,0 +1,219 @@ +#include "dialogs.hpp" +#include "map_pin.hpp" +#include "map_symbol.hpp" +#include "map_package.hpp" +#include "pool_browser_symbol.hpp" +#include "pool_browser_entity.hpp" +#include "pool_browser_padstack.hpp" +#include "pool_browser_part.hpp" +#include "component_pin_names.hpp" +#include "select_net.hpp" +#include "ask_net_merge.hpp" +#include "ask_delete_component.hpp" +#include "manage_buses.hpp" +#include "ask_datum.hpp" +#include "select_via_padstack.hpp" +#include "annotate.hpp" +#include "core/core.hpp" +#include "core/core_schematic.hpp" +#include "core/cores.hpp" +#include "part.hpp" + +namespace horizon { + void Dialogs::set_parent(Gtk::Window *w) { + parent = w; + } + + void Dialogs::set_core(Core *c) { + cores = std::make_unique(c); + } + + std::pair Dialogs::map_pin(const std::vector> &pins) { + MapPinDialog dia(parent, pins); + auto r = dia.run(); + if(r == Gtk::RESPONSE_OK) { + return {dia.selection_valid, dia.selected_uuid}; + } + else { + return {false, UUID()}; + } + } + std::pair> Dialogs::map_symbol(const std::map, std::string> &gates) { + MapSymbolDialog dia(parent, gates); + auto r = dia.run(); + if(r == Gtk::RESPONSE_OK) { + return {dia.selection_valid, dia.selected_uuid_path}; + } + else { + return {false, UUIDPath<2>()}; + } + } + + std::pair Dialogs::select_symbol(const UUID &unit_uuid) { + PoolBrowserDialogSymbol dia(parent, cores->r, unit_uuid); + auto r = dia.run(); + if(r == Gtk::RESPONSE_OK) { + return {dia.selection_valid, dia.selected_uuid}; + } + else { + return {false, UUID()}; + } + } + + std::pair Dialogs::select_padstack(const UUID &package_uuid) { + PoolBrowserDialogPadstack dia(parent, cores->r->m_pool, package_uuid); + auto r = dia.run(); + if(r == Gtk::RESPONSE_OK) { + return {dia.selection_valid, dia.selected_uuid}; + } + else { + return {false, UUID()}; + } + } + std::pair Dialogs::select_entity() { + PoolBrowserDialogEntity dia(parent, cores->r->m_pool); + auto r = dia.run(); + if(r == Gtk::RESPONSE_OK) { + return {dia.selection_valid, dia.selected_uuid}; + } + else { + return {false, UUID()}; + } + } + std::pair Dialogs::select_net(bool power_only) { + SelectNetDialog dia(parent, cores->c->get_schematic()->block, "Select net"); + dia.net_selector->set_power_only(power_only); + auto r = dia.run(); + if(r == Gtk::RESPONSE_OK) { + return {dia.valid, dia.net}; + } + else { + return {false, UUID()}; + } + } + std::pair Dialogs::select_bus() { + SelectNetDialog dia(parent, cores->c->get_schematic()->block, "Select bus"); + dia.net_selector->set_bus_mode(true); + auto r = dia.run(); + if(r == Gtk::RESPONSE_OK) { + return {dia.valid, dia.net}; + } + else { + return {false, UUID()}; + } + } + std::pair Dialogs::select_bus_member(const UUID &bus_uuid) { + SelectNetDialog dia(parent, cores->c->get_schematic()->block, "Select bus member"); + dia.net_selector->set_bus_member_mode(bus_uuid); + auto r = dia.run(); + if(r == Gtk::RESPONSE_OK) { + return {dia.valid, dia.net}; + } + else { + return {false, UUID()}; + } + } + + bool Dialogs::edit_component_pin_names(Component *comp) { + ComponentPinNamesDialog dia(parent, comp); + return dia.run() == Gtk::RESPONSE_OK; + } + + unsigned int Dialogs::ask_net_merge(Net *net, Net *into) { + AskNetMergeDialog dia(parent, net, into); + return dia.run(); + } + + bool Dialogs::ask_delete_component(Component *comp) { + AskDeleteComponentDialog dia(parent, comp); + return dia.run()==Gtk::RESPONSE_OK; + } + + bool Dialogs::manage_buses() { + ManageBusesDialog dia(parent, cores->c); + return dia.run()==Gtk::RESPONSE_OK; + } + + bool Dialogs::annotate() { + AnnotateDialog dia(parent, cores->c); + return dia.run()==Gtk::RESPONSE_OK; + } + + std::pair Dialogs::ask_datum(const std::string &label, int64_t def) { + AskDatumDialog dia(parent, label); + dia.sp->set_value(def); + dia.sp->select_region(0, -1); + auto r = dia.run(); + if(r == Gtk::RESPONSE_OK) { + return {true, dia.sp->get_value_as_int()}; + } + else { + return {false, 0}; + } + } + std::pair Dialogs::ask_datum_coord(const std::string &label, Coordi def) { + AskDatumDialog dia(parent, label, true); + dia.sp_x->set_value(def.x); + dia.sp_y->set_value(def.y); + dia.sp_x->select_region(0, -1); + auto r = dia.run(); + if(r == Gtk::RESPONSE_OK) { + return {true, {dia.sp_x->get_value_as_int(), dia.sp_y->get_value_as_int()}}; + } + else { + return {false, Coordi()}; + } + } + + std::tuple> Dialogs::ask_datum_coord2(const std::string &label, Coordi def) { + AskDatumDialog dia(parent, label, true); + dia.sp_x->set_value(def.x); + dia.sp_y->set_value(def.y); + dia.sp_x->select_region(0, -1); + auto r = dia.run(); + if(r == Gtk::RESPONSE_OK) { + return {true, {dia.sp_x->get_value_as_int(), dia.sp_y->get_value_as_int()}, {dia.cb_x->get_active(), dia.cb_y->get_active()}}; + } + else { + return {false, Coordi(), {false, false}}; + } + } + + std::pair Dialogs::select_part(const UUID &entity_uuid, const UUID &part_uuid) { + PoolBrowserDialogPart dia(parent, cores->r->m_pool, entity_uuid); + if(part_uuid) { + auto part = cores->r->m_pool->get_part(part_uuid); + dia.set_MPN(part->get_MPN()); + } + auto r = dia.run(); + if(r == Gtk::RESPONSE_OK) { + return {dia.selection_valid, dia.selected_uuid}; + } + else { + return {false, UUID()}; + } + } + + std::pair Dialogs::map_package(std::set &components) { + MapPackageDialog dia(parent, components); + auto r = dia.run(); + if(r == Gtk::RESPONSE_OK) { + return {dia.selection_valid, dia.selected_uuid}; + } + else { + return {false, UUID()}; + } + } + + std::pair Dialogs::select_via_padstack(class ViaPadstackProvider *vpp) { + SelectViaPadstackDialog dia(parent, vpp); + auto r = dia.run(); + if(r == Gtk::RESPONSE_OK) { + return {dia.selection_valid, dia.selected_uuid}; + } + else { + return {false, UUID()}; + } + } + +} diff --git a/dialogs/dialogs.hpp b/dialogs/dialogs.hpp new file mode 100644 index 000000000..ed765af13 --- /dev/null +++ b/dialogs/dialogs.hpp @@ -0,0 +1,45 @@ +#pragma once +#include +#include "uuid.hpp" +#include "common.hpp" +#include "uuid_path.hpp" +#include "component.hpp" +#include + +namespace Gtk { + class Window; +} + +namespace horizon { + class Dialogs { + public: + Dialogs() {}; + void set_parent(Gtk::Window *w); + void set_core(class Core *c); + + std::pair map_pin(const std::vector> &pins); + std::pair> map_symbol(const std::map, std::string> &gates); + std::pair map_package(std::set &components); + std::pair select_symbol(const UUID &unit_uuid); + std::pair select_part(const UUID &entity_uuid, const UUID &part_uuid); + std::pair select_entity(); + std::pair select_padstack(const UUID &package_uuid); + std::pair select_via_padstack(class ViaPadstackProvider *vpp); + std::pair select_net(bool power_only); + std::pair select_bus(); + std::pair select_bus_member(const UUID &bus_uuid); + bool edit_component_pin_names(class Component *comp); + unsigned int ask_net_merge(class Net *net, class Net *into); + bool ask_delete_component(Component *comp); + bool manage_buses(); + bool annotate(); + std::pair ask_datum(const std::string &label, int64_t def=0); + std::pair ask_datum_coord(const std::string &label, Coordi def=Coordi()); + std::tuple> ask_datum_coord2(const std::string &label, Coordi def=Coordi()); + + + private: + Gtk::Window *parent = nullptr; + std::unique_ptr cores = nullptr; + }; +} diff --git a/dialogs/manage_buses.cpp b/dialogs/manage_buses.cpp new file mode 100644 index 000000000..1bc8bc57e --- /dev/null +++ b/dialogs/manage_buses.cpp @@ -0,0 +1,258 @@ +#include "manage_buses.hpp" +#include "widgets/net_button.hpp" +#include +#include +#include + +namespace horizon { + + class BusMemberEditor: public Gtk::Box { + public: + BusMemberEditor(Bus *b, Bus::Member *m, CoreSchematic *c):Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4), bus(b), bus_member(m), core(c) { + set_margin_start(8); + set_margin_end(8); + set_margin_top(4); + set_margin_bottom(4); + + entry = Gtk::manage(new Gtk::Entry()); + entry->set_text(bus_member->name); + entry->set_width_chars(0); + entry->signal_changed().connect(sigc::mem_fun(this, &BusMemberEditor::member_name_changed)); + pack_start(*entry, true, true, 0); + + auto auto_name_button = Gtk::manage(new Gtk::Button()); + auto_name_button->set_image_from_icon_name("go-next-symbolic", Gtk::ICON_SIZE_BUTTON); + auto_name_button->signal_clicked().connect([this] { + bus_member->net->name = bus->name + "_" + bus_member->name; + net_button->update(); + }); + pack_start(*auto_name_button, false,false, 0); + + net_button = Gtk::manage(new NetButton(core->get_schematic()->block)); + net_button->set_net(bus_member->net->uuid); + net_button->signal_changed().connect(sigc::mem_fun(this, &BusMemberEditor::bus_net_changed)); + net_button->set_sensitive(!bus_member->net->has_bus_rippers); + pack_start(*net_button, false,false, 0); + + + auto delbutton = Gtk::manage(new Gtk::Button()); + delbutton->set_image_from_icon_name("list-remove-symbolic", Gtk::ICON_SIZE_BUTTON); + delbutton->set_sensitive(!bus_member->net->has_bus_rippers); + //delbutton->get_style_context()->add_class("destructive-action"); + delbutton->set_margin_start(16); + delbutton->signal_clicked().connect(sigc::mem_fun(this, &BusMemberEditor::bus_member_remove)); + pack_start(*delbutton, false, false, 0); + } + Gtk::Entry *entry; + NetButton *net_button; + private: + void member_name_changed() { + std::string new_name = entry->get_text(); + bus_member->name = new_name; + } + + void bus_net_changed(const UUID &uu) { + bus_member->net = &core->get_schematic()->block->nets.at(uu); + } + + void bus_member_remove() { + bus->members.erase(bus_member->uuid); + bus_member = nullptr; + delete this->get_parent(); + } + + Bus *bus; + Bus::Member *bus_member; + CoreSchematic *core; + }; + + + class BusEditor: public Gtk::Box { + public : + BusEditor(Bus *bu, CoreSchematic *c):Gtk::Box(Gtk::ORIENTATION_VERTICAL,0), bus(bu), core(c) { + auto labelbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4)); + labelbox->set_margin_start(12); + labelbox->set_margin_end(4); + labelbox->set_margin_top(4); + labelbox->set_margin_bottom(4); + auto la = Gtk::manage(new Gtk::Label("Bus Name")); + bus_name_entry = Gtk::manage(new Gtk::Entry()); + bus_name_entry->set_text(bus->name); + bus_name_entry->signal_changed().connect(sigc::mem_fun(this, &BusEditor::bus_name_changed)); + labelbox->pack_start(*la, false, false, 0); + labelbox->pack_start(*bus_name_entry, true, true, 0); + + + auto add_member_button = Gtk::manage(new Gtk::Button()); + add_member_button->set_margin_end(6); + add_member_button->set_margin_start(16); + add_member_button->set_image_from_icon_name("list-add-symbolic", Gtk::ICON_SIZE_BUTTON); + add_member_button->signal_clicked().connect(sigc::mem_fun(this, &BusEditor::bus_add_member)); + labelbox->pack_start(*add_member_button, false, false, 0); + + pack_start(*labelbox, false, false, 0); + + auto sc = Gtk::manage(new Gtk::ScrolledWindow()); + listbox = Gtk::manage(new Gtk::ListBox()); + listbox->set_header_func(sigc::mem_fun(this, &BusEditor::header_fun)); + listbox->set_selection_mode(Gtk::SELECTION_NONE); + sc->add(*listbox); + pack_start(*sc, true, true, 0); + + std::deque members_sorted; + for(auto &it: bus->members) { + members_sorted.push_back(&it.second); + } + std::sort(members_sorted.begin(), members_sorted.end(), [](const auto &a, const auto &b) {return a->name < b->name;}); + + sg_entry = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL); + sg_button= Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL); + for(auto it: members_sorted) { + add_row(it); + } + + } + + private : + Bus *bus; + CoreSchematic *core; + Gtk::Entry *bus_name_entry; + Gtk::ListBox *listbox; + Glib::RefPtr sg_entry; + Glib::RefPtr sg_button; + + void add_row(Bus::Member *mem) { + auto ed = Gtk::manage(new BusMemberEditor(bus, mem, core)); + sg_entry->add_widget(*ed->entry); + sg_button->add_widget(*ed->net_button); + listbox->insert(*ed, -1); + listbox->show_all(); + } + + void header_fun(Gtk::ListBoxRow *row, Gtk::ListBoxRow *before) { + if (before && !row->get_header()) { + auto ret = Gtk::manage(new Gtk::Separator); + row->set_header(*ret); + } + } + void bus_name_changed() { + std::string new_name = bus_name_entry->get_text(); + bus->name = new_name; + auto *stack = dynamic_cast(get_parent()); + stack->child_property_title(*this).set_value(new_name); + } + void bus_add_member() { + auto uu = UUID::random(); + Bus::Member *newmember = &bus->members.emplace(uu, uu).first->second; + newmember->name = "fixme"; + uu = UUID::random(); + Net *newnet = core->get_schematic()->block->insert_net(); + newnet->name = "changeme"; + newnet->is_bussed = true; + newmember->net = newnet; + add_row(newmember); + + } + }; + + + + + ManageBusesDialog::ManageBusesDialog(Gtk::Window *parent, CoreSchematic *c) : + Gtk::Dialog("Manage buses", *parent, Gtk::DialogFlags::DIALOG_MODAL|Gtk::DialogFlags::DIALOG_USE_HEADER_BAR), + core(c) + { + add_button("Cancel", Gtk::ResponseType::RESPONSE_CANCEL); + add_button("OK", Gtk::ResponseType::RESPONSE_OK); + set_default_response(Gtk::ResponseType::RESPONSE_OK); + set_default_size(400, 300); + + auto box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL,0)); + + stack = Gtk::manage(new Gtk::Stack()); + stack->set_transition_type(Gtk::STACK_TRANSITION_TYPE_SLIDE_RIGHT); + stack->set_transition_duration(100); + stack->property_visible_child_name().signal_changed().connect(sigc::mem_fun(this, &ManageBusesDialog::update_bus_removable)); + auto sidebar = Gtk::manage(new Gtk::StackSidebar()); + sidebar->set_stack(*stack); + + auto box2 = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL,0)); + box2->pack_start(*sidebar, true, true, 0); + + auto cssp = Gtk::CssProvider::create(); + cssp->load_from_data(".bus-toolbar { border-right: 1px solid @borders;}"); + Gtk::StyleContext::add_provider_for_screen(Gdk::Screen::get_default(), cssp, 0); + + auto tb = Gtk::manage(new Gtk::Toolbar()); + tb->set_icon_size(Gtk::ICON_SIZE_MENU); + tb->set_toolbar_style(Gtk::TOOLBAR_ICONS); + tb->get_style_context()->add_class("bus-toolbar"); + { + auto tbo = Gtk::manage(new Gtk::ToolButton()); + tbo->set_icon_name("list-add-symbolic"); + tbo->signal_clicked().connect(sigc::mem_fun(this, &ManageBusesDialog::add_bus)); + //tbo->signal_clicked().connect([this]{s_signal_add_sheet.emit();}); + tb->insert(*tbo, -1); + } + { + auto tbo = Gtk::manage(new Gtk::ToolButton()); + tbo->set_icon_name("list-remove-symbolic"); + tbo->signal_clicked().connect(sigc::mem_fun(this, &ManageBusesDialog::remove_bus)); + tb->insert(*tbo, -1); + delete_button = tbo; + } + + + box2->pack_start(*tb, false, false, 0); + + box->pack_start(*box2, false, false, 0); + box->pack_start(*stack, true, true, 0); + + std::deque buses_sorted; + for(auto &it: core->get_schematic()->block->buses) { + buses_sorted.push_back(&it.second); + } + std::sort(buses_sorted.begin(), buses_sorted.end(), [](const auto &a, const auto &b) {return a->name < b->name;}); + + + for(auto it: buses_sorted) { + auto ed = Gtk::manage(new BusEditor(it, core)); + //auto sc = Gtk::manage(new Gtk::ScrolledWindow()); + //sc->add(*ed); + stack->add(*ed, (std::string)it->uuid, it->name); + } + + + get_content_area()->pack_start(*box, true, true, 0); + get_content_area()->set_border_width(0); + + show_all(); + } + + void ManageBusesDialog::remove_bus() { + auto bus_current_uuid = UUID(stack->get_visible_child_name()); + const auto bus = core->get_schematic()->block->buses.at(bus_current_uuid); + if(bus.is_referenced) + return; + delete stack->get_visible_child(); + core->get_schematic()->block->buses.erase(bus_current_uuid); + } + + void ManageBusesDialog::update_bus_removable() { + auto bus_current_uuid = UUID(stack->get_visible_child_name()); + const auto bus = core->get_schematic()->block->buses.at(bus_current_uuid); + delete_button->set_sensitive(!bus.is_referenced); + } + + void ManageBusesDialog::add_bus() { + auto uu = UUID::random(); + Bus *newbus = &core->get_schematic()->block->buses.emplace(uu,uu).first->second; + newbus->name = "NEW"; + auto ed = Gtk::manage(new BusEditor(newbus, core)); + //auto sc = Gtk::manage(new Gtk::ScrolledWindow()); + //sc->add(*ed); + stack->add(*ed, (std::string)newbus->uuid, newbus->name); + stack->show_all(); + + } +} diff --git a/dialogs/manage_buses.hpp b/dialogs/manage_buses.hpp new file mode 100644 index 000000000..3c8daf700 --- /dev/null +++ b/dialogs/manage_buses.hpp @@ -0,0 +1,28 @@ +#pragma once +#include +#include +#include +#include "common.hpp" +#include "uuid.hpp" +#include "core/core_schematic.hpp" +namespace horizon { + + + class ManageBusesDialog: public Gtk::Dialog { + public: + ManageBusesDialog(Gtk::Window *parent, CoreSchematic *c); + bool valid = false; + + + private : + CoreSchematic *core = nullptr; + Gtk::Stack *stack; + Gtk::ToolButton *delete_button; + void add_bus(); + void remove_bus(); + void update_bus_removable(); + + + void ok_clicked(); + }; +} diff --git a/dialogs/map_package.cpp b/dialogs/map_package.cpp new file mode 100644 index 000000000..1a9abc6bc --- /dev/null +++ b/dialogs/map_package.cpp @@ -0,0 +1,60 @@ +#include "map_package.hpp" +#include "part.hpp" +#include + +namespace horizon { + + void MapPackageDialog::ok_clicked() { + auto it = view->get_selection()->get_selected(); + if(it) { + Gtk::TreeModel::Row row = *it; + std::cout << row[list_columns.name] << std::endl; + selection_valid = true; + selected_uuid = row[list_columns.uuid]; + } + } + + void MapPackageDialog::row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column) { + auto it = store->get_iter(path); + if(it) { + Gtk::TreeModel::Row row = *it; + std::cout << "act " << row[list_columns.name] << std::endl; + selection_valid = true; + selected_uuid = row[list_columns.uuid]; + response(Gtk::ResponseType::RESPONSE_OK); + } + } + + MapPackageDialog::MapPackageDialog(Gtk::Window *parent, std::set &components) : + Gtk::Dialog("Map Package", *parent, Gtk::DialogFlags::DIALOG_MODAL|Gtk::DialogFlags::DIALOG_USE_HEADER_BAR) { + Gtk::Button *button_ok = add_button("OK", Gtk::ResponseType::RESPONSE_OK); + add_button("Cancel", Gtk::ResponseType::RESPONSE_CANCEL); + set_default_response(Gtk::ResponseType::RESPONSE_OK); + set_default_size(300, 300); + + button_ok->signal_clicked().connect(sigc::mem_fun(this, &MapPackageDialog::ok_clicked)); + + store = Gtk::ListStore::create(list_columns); + Gtk::TreeModel::Row row; + for(auto it: components) { + row = *(store->append()); + row[list_columns.uuid] = it->uuid; + row[list_columns.name] = it->refdes; + row[list_columns.package] = it->part->package->name; + } + + view = Gtk::manage(new Gtk::TreeView(store)); + view->append_column("Component", list_columns.name); + view->append_column("Package", list_columns.package); + view->get_selection()->set_mode(Gtk::SelectionMode::SELECTION_BROWSE); + view->signal_row_activated().connect(sigc::mem_fun(this, &MapPackageDialog::row_activated)); + + + auto sc = Gtk::manage(new Gtk::ScrolledWindow()); + sc->add(*view); + get_content_area()->pack_start(*sc, true, true, 0); + get_content_area()->set_border_width(0); + + show_all(); + } +} diff --git a/dialogs/map_package.hpp b/dialogs/map_package.hpp new file mode 100644 index 000000000..9417e7bba --- /dev/null +++ b/dialogs/map_package.hpp @@ -0,0 +1,38 @@ +#pragma once +#include +#include +#include +#include "common.hpp" +#include "component.hpp" +#include "uuid.hpp" +namespace horizon { + + + class MapPackageDialog: public Gtk::Dialog { + public: + MapPackageDialog(Gtk::Window *parent, std::set &components); + UUID selected_uuid; + bool selection_valid = false; + //virtual ~MainWindow(); + private : + class ListColumns : public Gtk::TreeModelColumnRecord { + public: + ListColumns() { + Gtk::TreeModelColumnRecord::add( name ) ; + Gtk::TreeModelColumnRecord::add( package) ; + Gtk::TreeModelColumnRecord::add( uuid) ; + } + Gtk::TreeModelColumn name; + Gtk::TreeModelColumn package; + Gtk::TreeModelColumn uuid; + } ; + ListColumns list_columns; + + Gtk::TreeView *view; + Glib::RefPtr store; + + void ok_clicked(); + void row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column); + + }; +} diff --git a/dialogs/map_pin.cpp b/dialogs/map_pin.cpp new file mode 100644 index 000000000..9029a6ae8 --- /dev/null +++ b/dialogs/map_pin.cpp @@ -0,0 +1,58 @@ +#include "map_pin.hpp" +#include + +namespace horizon { + + void MapPinDialog::ok_clicked() { + auto it = view->get_selection()->get_selected(); + if(it) { + Gtk::TreeModel::Row row = *it; + std::cout << row[list_columns.name] << std::endl; + selection_valid = true; + selected_uuid = row[list_columns.uuid]; + } + } + + void MapPinDialog::row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column) { + auto it = store->get_iter(path); + if(it) { + Gtk::TreeModel::Row row = *it; + std::cout << "act " << row[list_columns.name] << std::endl; + selection_valid = true; + selected_uuid = row[list_columns.uuid]; + response(Gtk::ResponseType::RESPONSE_OK); + } + } + + MapPinDialog::MapPinDialog(Gtk::Window *parent, const std::vector> &pins) : + Gtk::Dialog("Map Pin", *parent, Gtk::DialogFlags::DIALOG_MODAL|Gtk::DialogFlags::DIALOG_USE_HEADER_BAR) { + Gtk::Button *button_ok = add_button("OK", Gtk::ResponseType::RESPONSE_OK); + add_button("Cancel", Gtk::ResponseType::RESPONSE_CANCEL); + set_default_response(Gtk::ResponseType::RESPONSE_OK); + set_default_size(300, 300); + + button_ok->signal_clicked().connect(sigc::mem_fun(this, &MapPinDialog::ok_clicked)); + + store = Gtk::ListStore::create(list_columns); + Gtk::TreeModel::Row row; + for(const auto &it: pins) { + if(it.second == false) { + row = *(store->append()); + row[list_columns.uuid] = it.first->uuid; + row[list_columns.name] = it.first->primary_name; + } + } + + view = Gtk::manage(new Gtk::TreeView(store)); + view->append_column("Pin", list_columns.name); + view->get_selection()->set_mode(Gtk::SelectionMode::SELECTION_BROWSE); + view->signal_row_activated().connect(sigc::mem_fun(this, &MapPinDialog::row_activated)); + + + auto sc = Gtk::manage(new Gtk::ScrolledWindow()); + sc->add(*view); + get_content_area()->pack_start(*sc, true, true, 0); + + show_all(); + } +} diff --git a/dialogs/map_pin.hpp b/dialogs/map_pin.hpp new file mode 100644 index 000000000..0ad480b55 --- /dev/null +++ b/dialogs/map_pin.hpp @@ -0,0 +1,37 @@ +#pragma once +#include +#include +#include +#include "common.hpp" +#include "uuid.hpp" +#include "unit.hpp" + +namespace horizon { + + + class MapPinDialog: public Gtk::Dialog { + public: + MapPinDialog(Gtk::Window *parent, const std::vector> &pins); + UUID selected_uuid; + bool selection_valid = false; + //virtual ~MainWindow(); + private : + class ListColumns : public Gtk::TreeModelColumnRecord { + public: + ListColumns() { + Gtk::TreeModelColumnRecord::add( name ) ; + Gtk::TreeModelColumnRecord::add( uuid ) ; + } + Gtk::TreeModelColumn name; + Gtk::TreeModelColumn uuid; + } ; + ListColumns list_columns; + + Gtk::TreeView *view; + Glib::RefPtr store; + + void ok_clicked(); + void row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column); + + }; +} diff --git a/dialogs/map_symbol.cpp b/dialogs/map_symbol.cpp new file mode 100644 index 000000000..14d0e608f --- /dev/null +++ b/dialogs/map_symbol.cpp @@ -0,0 +1,57 @@ +#include "map_symbol.hpp" +#include + +namespace horizon { + + void MapSymbolDialog::ok_clicked() { + auto it = view->get_selection()->get_selected(); + if(it) { + Gtk::TreeModel::Row row = *it; + std::cout << row[list_columns.name] << std::endl; + selection_valid = true; + selected_uuid_path = row[list_columns.uuid_path]; + } + } + + void MapSymbolDialog::row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column) { + auto it = store->get_iter(path); + if(it) { + Gtk::TreeModel::Row row = *it; + std::cout << "act " << row[list_columns.name] << std::endl; + selection_valid = true; + selected_uuid_path = row[list_columns.uuid_path]; + response(Gtk::ResponseType::RESPONSE_OK); + } + } + + MapSymbolDialog::MapSymbolDialog(Gtk::Window *parent, const std::map, std::string> &gates) : + Gtk::Dialog("Map Symbol", *parent, Gtk::DialogFlags::DIALOG_MODAL|Gtk::DialogFlags::DIALOG_USE_HEADER_BAR) { + Gtk::Button *button_ok = add_button("OK", Gtk::ResponseType::RESPONSE_OK); + add_button("Cancel", Gtk::ResponseType::RESPONSE_CANCEL); + set_default_response(Gtk::ResponseType::RESPONSE_OK); + set_default_size(300, 300); + + button_ok->signal_clicked().connect(sigc::mem_fun(this, &MapSymbolDialog::ok_clicked)); + + store = Gtk::ListStore::create(list_columns); + Gtk::TreeModel::Row row; + for(const auto &it: gates) { + row = *(store->append()); + row[list_columns.uuid_path] = it.first; + row[list_columns.name] = it.second; + } + + view = Gtk::manage(new Gtk::TreeView(store)); + view->append_column("Gate", list_columns.name); + view->get_selection()->set_mode(Gtk::SelectionMode::SELECTION_BROWSE); + view->signal_row_activated().connect(sigc::mem_fun(this, &MapSymbolDialog::row_activated)); + + + auto sc = Gtk::manage(new Gtk::ScrolledWindow()); + sc->add(*view); + get_content_area()->pack_start(*sc, true, true, 0); + get_content_area()->set_border_width(0); + + show_all(); + } +} diff --git a/dialogs/map_symbol.hpp b/dialogs/map_symbol.hpp new file mode 100644 index 000000000..fa0c85792 --- /dev/null +++ b/dialogs/map_symbol.hpp @@ -0,0 +1,36 @@ +#pragma once +#include +#include +#include +#include "common.hpp" +#include "uuid.hpp" +#include "uuid_path.hpp" +namespace horizon { + + + class MapSymbolDialog: public Gtk::Dialog { + public: + MapSymbolDialog(Gtk::Window *parent, const std::map, std::string> &gates); + UUIDPath<2> selected_uuid_path; + bool selection_valid = false; + //virtual ~MainWindow(); + private : + class ListColumns : public Gtk::TreeModelColumnRecord { + public: + ListColumns() { + Gtk::TreeModelColumnRecord::add( name ) ; + Gtk::TreeModelColumnRecord::add( uuid_path ) ; + } + Gtk::TreeModelColumn name; + Gtk::TreeModelColumn> uuid_path; + } ; + ListColumns list_columns; + + Gtk::TreeView *view; + Glib::RefPtr store; + + void ok_clicked(); + void row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column); + + }; +} diff --git a/dialogs/pool_browser.ui b/dialogs/pool_browser.ui new file mode 100644 index 000000000..442cfb591 --- /dev/null +++ b/dialogs/pool_browser.ui @@ -0,0 +1,96 @@ + + + + + + True + False + vertical + 4 + + + True + False + 4 + 4 + 4 + 4 + 4 + 4 + True + + + True + False + Name + 1 + + + 0 + 0 + + + + + True + False + Tags + 1 + + + 0 + 1 + + + + + True + True + True + + + 1 + 0 + + + + + True + True + + + 1 + 1 + + + + + False + True + 0 + + + + + True + True + never + in + + + True + True + + + + + + + + True + True + 1 + + + + diff --git a/dialogs/pool_browser_box.cpp b/dialogs/pool_browser_box.cpp new file mode 100644 index 000000000..6e90babe6 --- /dev/null +++ b/dialogs/pool_browser_box.cpp @@ -0,0 +1,25 @@ +#include "pool_browser_box.hpp" + +namespace horizon { + + PoolBrowserBox::PoolBrowserBox(BaseObjectType* cobject, const Glib::RefPtr& x) : + Gtk::Box(cobject) { + + show_all(); + } + + PoolBrowserBox* PoolBrowserBox::create() { + PoolBrowserBox* w; + Glib::RefPtr x = Gtk::Builder::create(); + x->add_from_resource("/net/carrotIndustries/horizon/dialogs/pool_browser.ui"); + x->get_widget_derived("browser_box", w); + x->get_widget("treeview", w->w_treeview); + x->get_widget("name_entry", w->w_name_entry); + x->get_widget("tags_entry", w->w_tags_entry); + + w->reference(); + return w; + } + + +} diff --git a/dialogs/pool_browser_box.hpp b/dialogs/pool_browser_box.hpp new file mode 100644 index 000000000..49e695fee --- /dev/null +++ b/dialogs/pool_browser_box.hpp @@ -0,0 +1,18 @@ +#pragma once +#include + +namespace horizon { + class PoolBrowserBox: public Gtk::Box { + public: + PoolBrowserBox(BaseObjectType* cobject, const Glib::RefPtr& x); + static PoolBrowserBox* create(); + Gtk::TreeView *w_treeview; + Gtk::Entry *w_name_entry; + Gtk::Entry *w_tags_entry; + + private : + + + }; + +} diff --git a/dialogs/pool_browser_entity.cpp b/dialogs/pool_browser_entity.cpp new file mode 100644 index 000000000..fdc6aa08b --- /dev/null +++ b/dialogs/pool_browser_entity.cpp @@ -0,0 +1,111 @@ +#include "pool_browser_entity.hpp" +#include "pool_browser_box.hpp" +#include +#include "sqlite.hpp" + +namespace horizon { + + void PoolBrowserDialogEntity::ok_clicked() { + auto it = box->w_treeview->get_selection()->get_selected(); + if(it) { + Gtk::TreeModel::Row row = *it; + selection_valid = true; + selected_uuid = row[list_columns.uuid]; + } + } + + void PoolBrowserDialogEntity::row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column) { + auto it = store->get_iter(path); + if(it) { + Gtk::TreeModel::Row row = *it; + selection_valid = true; + selected_uuid = row[list_columns.uuid]; + response(Gtk::ResponseType::RESPONSE_OK); + } + } + + bool PoolBrowserDialogEntity::auto_ok() { + response(Gtk::ResponseType::RESPONSE_OK); + return false; + } + + void PoolBrowserDialogEntity::search() { + store->freeze_notify(); + store->clear(); + Gtk::TreeModel::Row row; + std::string name_search = box->w_name_entry->get_text(); + + std::istringstream iss(box->w_tags_entry->get_text()); + std::set tags{std::istream_iterator{iss}, std::istream_iterator{}}; + std::string query; + if(tags.size() == 0) { + query = "SELECT entities.uuid, entities.name, entities.prefix, entities.n_gates, GROUP_CONCAT(tags.tag, ' ') FROM entities LEFT JOIN tags ON tags.uuid = entities.uuid WHERE entities.name LIKE ? GROUP by entities.uuid ORDER BY name"; + } + else { + std::ostringstream qs; + qs << "SELECT entities.uuid, entities.name, entities.prefix, entities.n_gates, (SELECT GROUP_CONCAT(tags.tag, ' ') FROM tags WHERE tags.uuid = entities.uuid) FROM entities LEFT JOIN tags ON tags.uuid = entities.uuid WHERE entities.name LIKE ? "; + qs << "AND ("; + for(const auto &it: tags) { + qs << "tags.tag LIKE ? OR "; + } + qs << "0) GROUP by entities.uuid HAVING count(*) >= $ntags ORDER BY name"; + query = qs.str(); + } + + SQLite::Query q(pool->db, query); + q.bind(1, "%"+name_search+"%"); + int i = 0; + for(const auto &it: tags) { + q.bind(i+2, it+"%"); + i++; + } + if(tags.size()) + q.bind("$ntags", tags.size()); + while(q.step()) { + row = *(store->append()); + row[list_columns.uuid] = q.get(0); + row[list_columns.entity_name] = q.get(1); + row[list_columns.prefix] = q.get(2); + row[list_columns.n_gates] = q.get(3); + row[list_columns.tags] = q.get(4); + } + store->thaw_notify(); + } + + PoolBrowserDialogEntity::PoolBrowserDialogEntity(Gtk::Window *parent, Pool *ipool) : + Gtk::Dialog("Select Entity", *parent, Gtk::DialogFlags::DIALOG_MODAL|Gtk::DialogFlags::DIALOG_USE_HEADER_BAR), + pool(ipool) + { + Gtk::Button *button_ok = add_button("OK", Gtk::ResponseType::RESPONSE_OK); + add_button("Cancel", Gtk::ResponseType::RESPONSE_CANCEL); + set_default_response(Gtk::ResponseType::RESPONSE_OK); + set_default_size(300, 300); + + button_ok->signal_clicked().connect(sigc::mem_fun(this, &PoolBrowserDialogEntity::ok_clicked)); + + store = Gtk::ListStore::create(list_columns); + + + box = PoolBrowserBox::create(); + get_content_area()->pack_start(*box, true, true, 0); + get_content_area()->set_border_width(0); + box->unreference(); + + search(); + + auto view = box->w_treeview; + view->set_model(store); + view->append_column("Entity Name", list_columns.entity_name); + view->append_column("Prefix", list_columns.prefix); + view->append_column("Gates", list_columns.n_gates); + view->append_column("Tags", list_columns.tags); + dynamic_cast(view->get_column_cell_renderer(3))->property_ellipsize() = Pango::ELLIPSIZE_END; + view->get_selection()->set_mode(Gtk::SelectionMode::SELECTION_BROWSE); + view->signal_row_activated().connect(sigc::mem_fun(this, &PoolBrowserDialogEntity::row_activated)); + + box->w_name_entry->signal_changed().connect(sigc::mem_fun(this, &PoolBrowserDialogEntity::search)); + box->w_tags_entry->signal_changed().connect(sigc::mem_fun(this, &PoolBrowserDialogEntity::search)); + + show_all(); + } +} diff --git a/dialogs/pool_browser_entity.hpp b/dialogs/pool_browser_entity.hpp new file mode 100644 index 000000000..8db3a81b4 --- /dev/null +++ b/dialogs/pool_browser_entity.hpp @@ -0,0 +1,50 @@ +#pragma once +#include +#include +#include +#include "common.hpp" +#include "uuid.hpp" +#include "uuid_path.hpp" +#include "pool.hpp" +#include "pool_browser_box.hpp" +namespace horizon { + + + class PoolBrowserDialogEntity: public Gtk::Dialog { + public: + PoolBrowserDialogEntity(Gtk::Window *parent, Pool *ipool); + UUID selected_uuid; + bool selection_valid = false; + //virtual ~MainWindow(); + private : + Pool *pool; + + class ListColumns : public Gtk::TreeModelColumnRecord { + public: + ListColumns() { + Gtk::TreeModelColumnRecord::add( entity_name ) ; + Gtk::TreeModelColumnRecord::add( prefix ) ; + Gtk::TreeModelColumnRecord::add( n_gates ) ; + Gtk::TreeModelColumnRecord::add( uuid ) ; + Gtk::TreeModelColumnRecord::add( tags ) ; + } + Gtk::TreeModelColumn entity_name; + Gtk::TreeModelColumn prefix; + Gtk::TreeModelColumn tags; + Gtk::TreeModelColumn uuid; + Gtk::TreeModelColumn n_gates; + } ; + ListColumns list_columns; + + PoolBrowserBox *box; + Glib::RefPtr store; + + void search(); + + + void ok_clicked(); + void row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column); + bool auto_ok(); + + }; +} diff --git a/dialogs/pool_browser_package.cpp b/dialogs/pool_browser_package.cpp new file mode 100644 index 000000000..5768b28f6 --- /dev/null +++ b/dialogs/pool_browser_package.cpp @@ -0,0 +1,108 @@ +#include "pool_browser_package.hpp" +#include +#include "sqlite.hpp" +#include "pool.hpp" + +namespace horizon { + + void PoolBrowserDialogPackage::ok_clicked() { + auto it = box->w_treeview->get_selection()->get_selected(); + if(it) { + Gtk::TreeModel::Row row = *it; + selection_valid = true; + selected_uuid = row[list_columns.uuid]; + } + } + + void PoolBrowserDialogPackage::row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column) { + auto it = store->get_iter(path); + if(it) { + Gtk::TreeModel::Row row = *it; + selection_valid = true; + selected_uuid = row[list_columns.uuid]; + response(Gtk::ResponseType::RESPONSE_OK); + } + } + + bool PoolBrowserDialogPackage::auto_ok() { + response(Gtk::ResponseType::RESPONSE_OK); + return false; + } + + void PoolBrowserDialogPackage::search() { + store->freeze_notify(); + store->clear(); + Gtk::TreeModel::Row row; + std::string name_search = box->w_name_entry->get_text(); + + std::istringstream iss(box->w_tags_entry->get_text()); + std::set tags{std::istream_iterator{iss}, std::istream_iterator{}}; + std::string query; + if(tags.size() == 0) { + query = "SELECT packages.uuid, packages.name, packages.n_pads, GROUP_CONCAT(tags.tag, ' ') FROM packages LEFT JOIN tags ON tags.uuid = packages.uuid WHERE packages.name LIKE ? GROUP by packages.uuid ORDER BY name"; + } + else { + std::ostringstream qs; + qs << "SELECT packages.uuid, packages.name, packages.n_pads, (SELECT GROUP_CONCAT(tags.tag, ' ') FROM tags WHERE tags.uuid = packages.uuid) FROM packages LEFT JOIN tags ON tags.uuid = packages.uuid WHERE packages.name LIKE ? "; + qs << "AND ("; + for(const auto &it: tags) { + qs << "tags.tag LIKE ? OR "; + } + qs << "0) GROUP by packages.uuid HAVING count(*) >= $ntags ORDER BY name"; + query = qs.str(); + } + SQLite::Query q(pool->db, query); + q.bind(1, "%"+name_search+"%"); + int i = 0; + for(const auto &it: tags) { + q.bind(i+2, it+"%"); + i++; + } + if(tags.size()) + q.bind("$ntags", tags.size()); + while(q.step()) { + row = *(store->append()); + row[list_columns.uuid] = q.get(0); + row[list_columns.package_name] = q.get(1); + row[list_columns.n_pads] = q.get(2); + row[list_columns.tags] = q.get(3); + } + store->thaw_notify(); + } + + PoolBrowserDialogPackage::PoolBrowserDialogPackage(Gtk::Window *parent, Pool *ipool) : + Gtk::Dialog("Select Package", *parent, Gtk::DialogFlags::DIALOG_MODAL|Gtk::DialogFlags::DIALOG_USE_HEADER_BAR), + pool(ipool) + { + Gtk::Button *button_ok = add_button("OK", Gtk::ResponseType::RESPONSE_OK); + add_button("Cancel", Gtk::ResponseType::RESPONSE_CANCEL); + set_default_response(Gtk::ResponseType::RESPONSE_OK); + set_default_size(300, 300); + + button_ok->signal_clicked().connect(sigc::mem_fun(this, &PoolBrowserDialogPackage::ok_clicked)); + + store = Gtk::ListStore::create(list_columns); + + + box = PoolBrowserBox::create(); + get_content_area()->pack_start(*box, true, true, 0); + get_content_area()->set_border_width(0); + box->unreference(); + + search(); + + auto view = box->w_treeview; + view->set_model(store); + view->append_column("Package Name", list_columns.package_name); + view->append_column("Pads", list_columns.n_pads); + view->append_column("Tags", list_columns.tags); + dynamic_cast(view->get_column_cell_renderer(2))->property_ellipsize() = Pango::ELLIPSIZE_END; + view->get_selection()->set_mode(Gtk::SelectionMode::SELECTION_BROWSE); + view->signal_row_activated().connect(sigc::mem_fun(this, &PoolBrowserDialogPackage::row_activated)); + + box->w_name_entry->signal_changed().connect(sigc::mem_fun(this, &PoolBrowserDialogPackage::search)); + box->w_tags_entry->signal_changed().connect(sigc::mem_fun(this, &PoolBrowserDialogPackage::search)); + + show_all(); + } +} diff --git a/dialogs/pool_browser_package.hpp b/dialogs/pool_browser_package.hpp new file mode 100644 index 000000000..c73372c33 --- /dev/null +++ b/dialogs/pool_browser_package.hpp @@ -0,0 +1,47 @@ +#pragma once +#include +#include +#include +#include "common.hpp" +#include "uuid.hpp" +#include "uuid_path.hpp" +#include "pool_browser_box.hpp" + +namespace horizon { + + + class PoolBrowserDialogPackage: public Gtk::Dialog { + public: + PoolBrowserDialogPackage(Gtk::Window *parent, class Pool *ipool); + UUID selected_uuid; + bool selection_valid = false; + //virtual ~MainWindow(); + private : + Pool *pool; + + class ListColumns : public Gtk::TreeModelColumnRecord { + public: + ListColumns() { + Gtk::TreeModelColumnRecord::add( package_name ) ; + Gtk::TreeModelColumnRecord::add( n_pads ) ; + Gtk::TreeModelColumnRecord::add( uuid ) ; + Gtk::TreeModelColumnRecord::add( tags) ; + } + Gtk::TreeModelColumn package_name; + Gtk::TreeModelColumn tags; + Gtk::TreeModelColumn uuid; + Gtk::TreeModelColumn n_pads; + } ; + ListColumns list_columns; + + PoolBrowserBox *box; + Glib::RefPtr store; + + + void search(); + void ok_clicked(); + void row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column); + bool auto_ok(); + + }; +} diff --git a/dialogs/pool_browser_padstack.cpp b/dialogs/pool_browser_padstack.cpp new file mode 100644 index 000000000..20ad574bd --- /dev/null +++ b/dialogs/pool_browser_padstack.cpp @@ -0,0 +1,74 @@ +#include "pool_browser_padstack.hpp" +#include +#include "sqlite.hpp" + +namespace horizon { + + void PoolBrowserDialogPadstack::ok_clicked() { + auto it = view->get_selection()->get_selected(); + if(it) { + Gtk::TreeModel::Row row = *it; + selection_valid = true; + selected_uuid = row[list_columns.uuid]; + } + } + + void PoolBrowserDialogPadstack::row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column) { + auto it = store->get_iter(path); + if(it) { + Gtk::TreeModel::Row row = *it; + selection_valid = true; + selected_uuid = row[list_columns.uuid]; + response(Gtk::ResponseType::RESPONSE_OK); + } + } + + bool PoolBrowserDialogPadstack::auto_ok() { + response(Gtk::ResponseType::RESPONSE_OK); + return false; + } + + PoolBrowserDialogPadstack::PoolBrowserDialogPadstack(Gtk::Window *parent, Pool *ipool, const UUID &pkg_uuid) : + Gtk::Dialog("Select Padstack", *parent, Gtk::DialogFlags::DIALOG_MODAL|Gtk::DialogFlags::DIALOG_USE_HEADER_BAR), + pool(ipool), + package_uuid(pkg_uuid){ + Gtk::Button *button_ok = add_button("OK", Gtk::ResponseType::RESPONSE_OK); + add_button("Cancel", Gtk::ResponseType::RESPONSE_CANCEL); + set_default_response(Gtk::ResponseType::RESPONSE_OK); + set_default_size(300, 300); + + button_ok->signal_clicked().connect(sigc::mem_fun(this, &PoolBrowserDialogPadstack::ok_clicked)); + + store = Gtk::ListStore::create(list_columns); + Gtk::TreeModel::Row row; + SQLite::Query q(pool->db, "SELECT padstacks.uuid, padstacks.name FROM padstacks WHERE padstacks.package=?"); + q.bind(1, package_uuid); + unsigned int n = 0; + while(q.step()) { + row = *(store->append()); + row[list_columns.uuid] = q.get(0); + row[list_columns.padstack_name] = q.get(1); + n++; + } + /*if(n == 1) { + selection_valid = true; + selected_uuid = row[list_columns.uuid]; + Glib::signal_idle().connect( sigc::mem_fun(this, &PoolBrowserDialogSymbol::auto_ok)); + return; + }*/ + + + view = Gtk::manage(new Gtk::TreeView(store)); + view->append_column("Padstack Name", list_columns.padstack_name); + view->get_selection()->set_mode(Gtk::SelectionMode::SELECTION_BROWSE); + view->signal_row_activated().connect(sigc::mem_fun(this, &PoolBrowserDialogPadstack::row_activated)); + + + auto sc = Gtk::manage(new Gtk::ScrolledWindow()); + sc->add(*view); + get_content_area()->pack_start(*sc, true, true, 0); + get_content_area()->set_border_width(0); + + show_all(); + } +} diff --git a/dialogs/pool_browser_padstack.hpp b/dialogs/pool_browser_padstack.hpp new file mode 100644 index 000000000..c30319332 --- /dev/null +++ b/dialogs/pool_browser_padstack.hpp @@ -0,0 +1,44 @@ +#pragma once +#include +#include +#include +#include "common.hpp" +#include "uuid.hpp" +#include "uuid_path.hpp" +#include "pool.hpp" +namespace horizon { + + + class PoolBrowserDialogPadstack: public Gtk::Dialog { + public: + PoolBrowserDialogPadstack(Gtk::Window *parent, Pool *ipool, const UUID &ipackage_uuid); + UUID selected_uuid; + ObjectType selected_object_type; + bool selection_valid = false; + //virtual ~MainWindow(); + private : + Pool *pool; + UUID package_uuid; + + class ListColumns : public Gtk::TreeModelColumnRecord { + public: + ListColumns() { + Gtk::TreeModelColumnRecord::add( padstack_name ) ; + Gtk::TreeModelColumnRecord::add( uuid ) ; + } + Gtk::TreeModelColumn padstack_name; + Gtk::TreeModelColumn uuid; + } ; + ListColumns list_columns; + + Gtk::TreeView *view; + Glib::RefPtr store; + + + + void ok_clicked(); + void row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column); + bool auto_ok(); + + }; +} diff --git a/dialogs/pool_browser_part.cpp b/dialogs/pool_browser_part.cpp new file mode 100644 index 000000000..829a14b88 --- /dev/null +++ b/dialogs/pool_browser_part.cpp @@ -0,0 +1,171 @@ +#include "pool_browser_part.hpp" +#include +#include + +namespace horizon { + + + class PoolBrowserBoxPart: public Gtk::Box { + public: + PoolBrowserBoxPart(BaseObjectType* cobject, const Glib::RefPtr& x) : + Gtk::Box(cobject) { + show_all(); + } + static PoolBrowserBoxPart* create() { + PoolBrowserBoxPart* w; + Glib::RefPtr x = Gtk::Builder::create(); + x->add_from_resource("/net/carrotIndustries/horizon/dialogs/pool_browser_part.ui"); + x->get_widget_derived("browser_box", w); + x->get_widget("treeview", w->w_treeview); + x->get_widget("MPN_entry", w->w_MPN_entry); + x->get_widget("manufacturer_entry", w->w_manufacturer_entry); + x->get_widget("tags_entry", w->w_tags_entry); + + w->reference(); + return w; + } + Gtk::TreeView *w_treeview; + Gtk::Entry *w_MPN_entry; + Gtk::Entry *w_manufacturer_entry; + Gtk::Entry *w_tags_entry; + + private : + }; + + + + void PoolBrowserDialogPart::ok_clicked() { + auto it = box->w_treeview->get_selection()->get_selected(); + if(it) { + Gtk::TreeModel::Row row = *it; + selection_valid = true; + selected_uuid = row[list_columns.uuid]; + } + } + + void PoolBrowserDialogPart::row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column) { + auto it = store->get_iter(path); + if(it) { + Gtk::TreeModel::Row row = *it; + selection_valid = true; + selected_uuid = row[list_columns.uuid]; + response(Gtk::ResponseType::RESPONSE_OK); + } + } + + bool PoolBrowserDialogPart::auto_ok() { + response(Gtk::ResponseType::RESPONSE_OK); + return false; + } + + void PoolBrowserDialogPart::search() { + store->freeze_notify(); + store->clear(); + Gtk::TreeModel::Row row; + std::string MPN_search = box->w_MPN_entry->get_text(); + std::string manufacturer_search = box->w_manufacturer_entry->get_text(); + + std::istringstream iss(box->w_tags_entry->get_text()); + std::set tags{std::istream_iterator{iss}, std::istream_iterator{}}; + std::stringstream query; + if(tags.size() == 0) { + query << "SELECT parts.uuid, parts.MPN, parts.manufacturer, packages.name, GROUP_CONCAT(tags.tag, ' ') FROM parts LEFT JOIN tags ON tags.uuid = parts.uuid LEFT JOIN packages ON packages.uuid = parts.package WHERE parts.MPN LIKE ? AND parts.manufacturer LIKE ? AND (parts.entity=? or ?) GROUP BY parts.uuid "; + } + else { + query << "SELECT parts.uuid, parts.MPN, parts.manufacturer, packages.name, (SELECT GROUP_CONCAT(tags.tag, ' ') FROM tags WHERE tags.uuid = parts.uuid) FROM parts LEFT JOIN tags ON tags.uuid = parts.uuid LEFT JOIN packages ON packages.uuid = parts.package WHERE parts.MPN LIKE ? AND parts.manufacturer LIKE ? AND (parts.entity=? or ?) "; + query << "AND ("; + for(const auto &it: tags) { + query << "tags.tag LIKE ? OR "; + } + query << "0) GROUP by parts.uuid HAVING count(*) >= $ntags "; + } + query << sort_controller->get_order_by(); + std::cout << query.str() << std::endl; + SQLite::Query q(pool->db, query.str()); + q.bind(1, "%"+MPN_search+"%"); + q.bind(2, "%"+manufacturer_search+"%"); + q.bind(3, entity_uuid); + q.bind(4, entity_uuid==UUID()); + int i = 0; + for(const auto &it: tags) { + q.bind(i+5, it+"%"); + i++; + } + if(tags.size()) + q.bind("$ntags", tags.size()); + + row = *(store->append()); + row[list_columns.uuid] = UUID(); + row[list_columns.MPN] = "none"; + row[list_columns.manufacturer] = "none"; + row[list_columns.package] = "none"; + + while(q.step()) { + row = *(store->append()); + row[list_columns.uuid] = q.get(0); + row[list_columns.MPN] = q.get(1); + row[list_columns.manufacturer] = q.get(2); + row[list_columns.package] = q.get(3); + row[list_columns.tags] = q.get(4); + } + store->thaw_notify(); + } + + void PoolBrowserDialogPart::set_MPN(const std::string &MPN) { + box->w_MPN_entry->set_text(MPN); + search(); + } + + PoolBrowserDialogPart::PoolBrowserDialogPart(Gtk::Window *parent, Pool *ipool, const UUID &ientity_uuid) : + Gtk::Dialog("Select Part", *parent, Gtk::DialogFlags::DIALOG_MODAL|Gtk::DialogFlags::DIALOG_USE_HEADER_BAR), + pool(ipool), + entity_uuid(ientity_uuid){ + + Gtk::Button *button_ok = add_button("OK", Gtk::ResponseType::RESPONSE_OK); + add_button("Cancel", Gtk::ResponseType::RESPONSE_CANCEL); + set_default_response(Gtk::ResponseType::RESPONSE_OK); + set_default_size(300, 300); + + button_ok->signal_clicked().connect(sigc::mem_fun(this, &PoolBrowserDialogPart::ok_clicked)); + + store = Gtk::ListStore::create(list_columns); + + + box = PoolBrowserBoxPart::create(); + get_content_area()->pack_start(*box, true, true, 0); + get_content_area()->set_border_width(0); + box->unreference(); + + + + auto view = box->w_treeview; + view->set_model(store); + view->append_column("MPN", list_columns.MPN); + view->append_column("Manufacturer", list_columns.manufacturer); + view->append_column("Package", list_columns.package); + view->append_column("Tags", list_columns.tags); + view->get_selection()->set_mode(Gtk::SelectionMode::SELECTION_BROWSE); + + sort_controller = std::make_unique(view); + sort_controller->set_simple(true); + sort_controller->add_column(0, "parts.MPN"); + sort_controller->add_column(1, "parts.manufacturer"); + sort_controller->add_column(2, "packages.name"); + sort_controller->set_sort(0, SortController::Sort::ASC); + sort_controller->signal_changed().connect(sigc::mem_fun(this, &PoolBrowserDialogPart::search)); + + + search(); + + dynamic_cast(view->get_column_cell_renderer(3))->property_ellipsize() = Pango::ELLIPSIZE_END; + view->get_selection()->set_mode(Gtk::SelectionMode::SELECTION_BROWSE); + view->signal_row_activated().connect(sigc::mem_fun(this, &PoolBrowserDialogPart::row_activated)); + + box->w_MPN_entry->signal_changed().connect(sigc::mem_fun(this, &PoolBrowserDialogPart::search)); + box->w_manufacturer_entry->signal_changed().connect(sigc::mem_fun(this, &PoolBrowserDialogPart::search)); + box->w_tags_entry->signal_changed().connect(sigc::mem_fun(this, &PoolBrowserDialogPart::search)); + + show_all(); + + } +} diff --git a/dialogs/pool_browser_part.hpp b/dialogs/pool_browser_part.hpp new file mode 100644 index 000000000..a6b24f502 --- /dev/null +++ b/dialogs/pool_browser_part.hpp @@ -0,0 +1,52 @@ +#pragma once +#include +#include +#include +#include "common.hpp" +#include "uuid.hpp" +#include "uuid_path.hpp" +#include "pool.hpp" +#include "sort_controller.hpp" +namespace horizon { + + + class PoolBrowserDialogPart: public Gtk::Dialog { + public: + PoolBrowserDialogPart(Gtk::Window *parent, Pool *ipool, const UUID &ientity_uuid); + UUID selected_uuid; + ObjectType selected_object_type; + bool selection_valid = false; + void set_MPN(const std::string &MPN); + //virtual ~MainWindow(); + private : + Pool *pool; + UUID entity_uuid; + + class ListColumns : public Gtk::TreeModelColumnRecord { + public: + ListColumns() { + Gtk::TreeModelColumnRecord::add( MPN ) ; + Gtk::TreeModelColumnRecord::add( manufacturer ) ; + Gtk::TreeModelColumnRecord::add( package ) ; + Gtk::TreeModelColumnRecord::add( uuid ) ; + Gtk::TreeModelColumnRecord::add( tags) ; + } + Gtk::TreeModelColumn MPN; + Gtk::TreeModelColumn manufacturer; + Gtk::TreeModelColumn package; + Gtk::TreeModelColumn tags; + Gtk::TreeModelColumn uuid; + } ; + ListColumns list_columns; + + class PoolBrowserBoxPart *box; + Glib::RefPtr store; + std::unique_ptr sort_controller; + + void ok_clicked(); + void row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column); + bool auto_ok(); + void search(); + + }; +} diff --git a/dialogs/pool_browser_part.ui b/dialogs/pool_browser_part.ui new file mode 100644 index 000000000..2b65cc1dc --- /dev/null +++ b/dialogs/pool_browser_part.ui @@ -0,0 +1,119 @@ + + + + + + True + False + vertical + 4 + + + True + False + 4 + 4 + 4 + 4 + 4 + 4 + True + + + True + False + MPN + 1 + + + 0 + 0 + + + + + True + False + Tags + 1 + + + 0 + 2 + + + + + True + True + True + + + 1 + 0 + + + + + True + True + + + 1 + 2 + + + + + True + False + Manufacturer + 1 + + + 0 + 1 + + + + + True + True + True + + + 1 + 1 + + + + + False + True + 0 + + + + + True + True + never + in + + + True + True + + + + + + + + True + True + 1 + + + + diff --git a/dialogs/pool_browser_symbol.cpp b/dialogs/pool_browser_symbol.cpp new file mode 100644 index 000000000..94e4d8892 --- /dev/null +++ b/dialogs/pool_browser_symbol.cpp @@ -0,0 +1,101 @@ +#include "pool_browser_symbol.hpp" +#include +#include "sqlite.hpp" +#include "canvas/canvas.hpp" + +namespace horizon { + + void PoolBrowserDialogSymbol::ok_clicked() { + auto it = view->get_selection()->get_selected(); + if(it) { + Gtk::TreeModel::Row row = *it; + selection_valid = true; + selected_uuid = row[list_columns.uuid]; + } + } + + void PoolBrowserDialogSymbol::row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column) { + auto it = store->get_iter(path); + if(it) { + Gtk::TreeModel::Row row = *it; + selection_valid = true; + selected_uuid = row[list_columns.uuid]; + response(Gtk::ResponseType::RESPONSE_OK); + } + } + + bool PoolBrowserDialogSymbol::auto_ok() { + response(Gtk::ResponseType::RESPONSE_OK); + return false; + } + + PoolBrowserDialogSymbol::PoolBrowserDialogSymbol(Gtk::Window *parent, Core *c, const UUID &iunit_uuid) : + Gtk::Dialog("Select Symbol", *parent, Gtk::DialogFlags::DIALOG_MODAL|Gtk::DialogFlags::DIALOG_USE_HEADER_BAR), + pool(c->m_pool), + core(c), + unit_uuid(iunit_uuid){ + Gtk::Button *button_ok = add_button("OK", Gtk::ResponseType::RESPONSE_OK); + add_button("Cancel", Gtk::ResponseType::RESPONSE_CANCEL); + set_default_response(Gtk::ResponseType::RESPONSE_OK); + set_default_size(600, 300); + + button_ok->signal_clicked().connect(sigc::mem_fun(this, &PoolBrowserDialogSymbol::ok_clicked)); + + store = Gtk::ListStore::create(list_columns); + + SQLite::Query q(pool->db, "SELECT symbols.uuid, symbols.name, units.name FROM symbols,units WHERE symbols.unit = units.uuid AND units.uuid=? ORDER BY symbols.name"); + q.bind(1, unit_uuid); + Gtk::TreeModel::Row row; + unsigned int n = 0; + while(q.step()) { + row = *(store->append()); + row[list_columns.uuid] = q.get(0); + row[list_columns.symbol_name] = q.get(1); + row[list_columns.unit_name] = q.get(2); + n++; + } + if(n == 1) { + selection_valid = true; + selected_uuid = row[list_columns.uuid]; + Glib::signal_idle().connect( sigc::mem_fun(this, &PoolBrowserDialogSymbol::auto_ok)); + return; + } + + + view = Gtk::manage(new Gtk::TreeView(store)); + view->append_column("Symbol Name", list_columns.symbol_name); + view->append_column("Unit Name", list_columns.unit_name); + view->get_selection()->set_mode(Gtk::SelectionMode::SELECTION_BROWSE); + view->signal_row_activated().connect(sigc::mem_fun(this, &PoolBrowserDialogSymbol::row_activated)); + view->get_selection()->signal_changed().connect([this]{ + auto it = view->get_selection()->get_selected(); + if(it) { + Gtk::TreeModel::Row r = *it; + Symbol sym = *pool->get_symbol(r[list_columns.uuid]); + sym.expand(); + canvas->update(sym); + auto bbox = sym.get_bbox(); + canvas->zoom_to_bbox(bbox.first, bbox.second); + } + }); + + + auto sc = Gtk::manage(new Gtk::ScrolledWindow()); + sc->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC); + sc->add(*view); + auto box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0)); + box->pack_start(*sc, false, false, 0); + box->pack_start(*Gtk::manage(new Gtk::Separator(Gtk::ORIENTATION_VERTICAL)), false, false, 0); + canvas = Gtk::manage(new CanvasGL()); + canvas->set_core(core); + canvas->set_selection_allowed(false); + box->pack_start(*canvas, true, true, 0); + + + + get_content_area()->pack_start(*box, true, true, 0); + get_content_area()->set_border_width(0); + + show_all(); + } +} diff --git a/dialogs/pool_browser_symbol.hpp b/dialogs/pool_browser_symbol.hpp new file mode 100644 index 000000000..646b3e258 --- /dev/null +++ b/dialogs/pool_browser_symbol.hpp @@ -0,0 +1,49 @@ +#pragma once +#include +#include +#include +#include "common.hpp" +#include "uuid.hpp" +#include "uuid_path.hpp" +#include "pool.hpp" +#include "core/core.hpp" +namespace horizon { + + + class PoolBrowserDialogSymbol: public Gtk::Dialog { + public: + PoolBrowserDialogSymbol(Gtk::Window *parent, Core *core, const UUID &iunit_uuid); + UUID selected_uuid; + ObjectType selected_object_type; + bool selection_valid = false; + //virtual ~MainWindow(); + private : + Pool *pool; + Core *core; + UUID unit_uuid; + + class ListColumns : public Gtk::TreeModelColumnRecord { + public: + ListColumns() { + Gtk::TreeModelColumnRecord::add( symbol_name ) ; + Gtk::TreeModelColumnRecord::add( unit_name ) ; + Gtk::TreeModelColumnRecord::add( uuid ) ; + } + Gtk::TreeModelColumn unit_name; + Gtk::TreeModelColumn symbol_name; + Gtk::TreeModelColumn uuid; + } ; + ListColumns list_columns; + + Gtk::TreeView *view; + class CanvasGL *canvas; + Glib::RefPtr store; + + + + void ok_clicked(); + void row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column); + bool auto_ok(); + + }; +} diff --git a/dialogs/select_net.cpp b/dialogs/select_net.cpp new file mode 100644 index 000000000..b499ffed8 --- /dev/null +++ b/dialogs/select_net.cpp @@ -0,0 +1,43 @@ +#include "select_net.hpp" +#include + +namespace horizon { + + void SelectNetDialog::ok_clicked() { + auto n = net_selector->get_selected_net(); + if(n) { + valid = true; + net = n; + response(Gtk::ResponseType::RESPONSE_OK); + } + + } + + void SelectNetDialog::net_selected(const UUID &uu) { + valid = true; + net = uu; + response(Gtk::ResponseType::RESPONSE_OK); + } + + + SelectNetDialog::SelectNetDialog(Gtk::Window *parent, Block *b, const std::string &ti) : + Gtk::Dialog(ti, *parent, Gtk::DialogFlags::DIALOG_MODAL|Gtk::DialogFlags::DIALOG_USE_HEADER_BAR), + block(b) + { + add_button("Cancel", Gtk::ResponseType::RESPONSE_CANCEL); + auto ok_button = add_button("OK", Gtk::ResponseType::RESPONSE_OK); + set_default_response(Gtk::ResponseType::RESPONSE_OK); + set_position(Gtk::WIN_POS_MOUSE); + set_default_size(300, 300); + + net_selector = Gtk::manage(new NetSelector(b)); + net_selector->signal_activated().connect(sigc::mem_fun(this, &SelectNetDialog::net_selected)); + ok_button->signal_clicked().connect([this]{net = net_selector->get_selected_net(); valid=net!=UUID();}); + + + get_content_area()->pack_start(*net_selector, true, true, 0); + get_content_area()->set_border_width(0); + + show_all(); + } +} diff --git a/dialogs/select_net.hpp b/dialogs/select_net.hpp new file mode 100644 index 000000000..ab7302884 --- /dev/null +++ b/dialogs/select_net.hpp @@ -0,0 +1,27 @@ +#pragma once +#include +#include +#include +#include "common.hpp" +#include "uuid.hpp" +#include "net.hpp" +#include "widgets/net_selector.hpp" +namespace horizon { + + + class SelectNetDialog: public Gtk::Dialog { + public: + SelectNetDialog(Gtk::Window *parent, Block *b, const std::string &ti); + bool valid = false; + UUID net; + NetSelector *net_selector; + + + private : + Block *block = nullptr; + + + void ok_clicked(); + void net_selected(const UUID &uu); + }; +} diff --git a/dialogs/select_via_padstack.cpp b/dialogs/select_via_padstack.cpp new file mode 100644 index 000000000..a13302519 --- /dev/null +++ b/dialogs/select_via_padstack.cpp @@ -0,0 +1,58 @@ +#include "select_via_padstack.hpp" +#include "via_padstack_provider.hpp" +#include + +namespace horizon { + + void SelectViaPadstackDialog::ok_clicked() { + auto it = view->get_selection()->get_selected(); + if(it) { + Gtk::TreeModel::Row row = *it; + std::cout << row[list_columns.name] << std::endl; + selection_valid = true; + selected_uuid = row[list_columns.uuid]; + } + } + + void SelectViaPadstackDialog::row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column) { + auto it = store->get_iter(path); + if(it) { + Gtk::TreeModel::Row row = *it; + std::cout << "act " << row[list_columns.name] << std::endl; + selection_valid = true; + selected_uuid = row[list_columns.uuid]; + response(Gtk::ResponseType::RESPONSE_OK); + } + } + + SelectViaPadstackDialog::SelectViaPadstackDialog(Gtk::Window *parent, ViaPadstackProvider *vpp) : + Gtk::Dialog("Select Via Padstack", *parent, Gtk::DialogFlags::DIALOG_MODAL|Gtk::DialogFlags::DIALOG_USE_HEADER_BAR), + via_padstack_provider(vpp){ + Gtk::Button *button_ok = add_button("OK", Gtk::ResponseType::RESPONSE_OK); + add_button("Cancel", Gtk::ResponseType::RESPONSE_CANCEL); + set_default_response(Gtk::ResponseType::RESPONSE_OK); + set_default_size(300, 300); + + button_ok->signal_clicked().connect(sigc::mem_fun(this, &SelectViaPadstackDialog::ok_clicked)); + + store = Gtk::ListStore::create(list_columns); + Gtk::TreeModel::Row row; + for(const auto &it: via_padstack_provider->get_padstacks_available()) { + row = *(store->append()); + row[list_columns.uuid] = it.first; + row[list_columns.name] = it.second.name; + } + + view = Gtk::manage(new Gtk::TreeView(store)); + view->append_column("Padstack", list_columns.name); + view->get_selection()->set_mode(Gtk::SelectionMode::SELECTION_BROWSE); + view->signal_row_activated().connect(sigc::mem_fun(this, &SelectViaPadstackDialog::row_activated)); + + + auto sc = Gtk::manage(new Gtk::ScrolledWindow()); + sc->add(*view); + get_content_area()->pack_start(*sc, true, true, 0); + + show_all(); + } +} diff --git a/dialogs/select_via_padstack.hpp b/dialogs/select_via_padstack.hpp new file mode 100644 index 000000000..9c1953364 --- /dev/null +++ b/dialogs/select_via_padstack.hpp @@ -0,0 +1,37 @@ +#pragma once +#include +#include +#include +#include "common.hpp" +#include "uuid.hpp" +namespace horizon { + + + class SelectViaPadstackDialog: public Gtk::Dialog { + public: + SelectViaPadstackDialog(Gtk::Window *parent, class ViaPadstackProvider *vpp); + UUID selected_uuid; + bool selection_valid = false; + //virtual ~MainWindow(); + private : + + ViaPadstackProvider *via_padstack_provider; + + class ListColumns : public Gtk::TreeModelColumnRecord { + public: + ListColumns() { + Gtk::TreeModelColumnRecord::add( name ) ; + Gtk::TreeModelColumnRecord::add( uuid ) ; + } + Gtk::TreeModelColumn name; + Gtk::TreeModelColumn uuid; + } ; + ListColumns list_columns; + + Gtk::TreeView *view; + Glib::RefPtr store; + + void ok_clicked(); + void row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column); + }; +} diff --git a/export_gerber/cam_job.cpp b/export_gerber/cam_job.cpp new file mode 100644 index 000000000..d5bb32de2 --- /dev/null +++ b/export_gerber/cam_job.cpp @@ -0,0 +1,32 @@ +#include "cam_job.hpp" +#include "json.hpp" +#include + +namespace horizon { + CAMJob::CAMJob(const json &j) : + title(j.at("title").get()), + drill_pth(j.at("drill_pth").get()), + drill_npth(j.at("drill_npth").get()), + merge_npth(j.at("merge_npth")) + { + { + const json &o = j["layers"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + int n = std::stoi(it.key()); + layers.emplace(n, it.value().get()); + } + } + } + + CAMJob CAMJob::new_from_file(const std::string &filename) { + json j; + std::ifstream ifs(filename); + if(!ifs.is_open()) { + throw std::runtime_error("file " +filename+ " not opened"); + } + ifs>>j; + ifs.close(); + return CAMJob(j); + } + +} diff --git a/export_gerber/cam_job.hpp b/export_gerber/cam_job.hpp new file mode 100644 index 000000000..9badfe2b7 --- /dev/null +++ b/export_gerber/cam_job.hpp @@ -0,0 +1,19 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +namespace horizon { + using json = nlohmann::json; + + class CAMJob { + public : + CAMJob(const json &); + static CAMJob new_from_file(const std::string &filename); + + std::string title; + std::map layers; + std::string drill_pth; + std::string drill_npth; + bool merge_npth = false; + }; + +} diff --git a/export_gerber/canvas_gerber.cpp b/export_gerber/canvas_gerber.cpp new file mode 100644 index 000000000..e86eeaf19 --- /dev/null +++ b/export_gerber/canvas_gerber.cpp @@ -0,0 +1,50 @@ +#include "canvas_gerber.hpp" +#include "core/core_board.hpp" +#include "gerber_export.hpp" + +namespace horizon { + CanvasGerber::CanvasGerber(GerberExporter *exp) : Canvas::Canvas(), exporter(exp) { + img_mode = true; + } + void CanvasGerber::request_push() {} + + void CanvasGerber::img_net(const Net *n) { + + } + + void CanvasGerber::img_polygon(const Polygon &ipoly) { + auto poly = ipoly.remove_arcs(16); + if(poly.layer == 50) { //outline + auto last = poly.vertices.cbegin(); + for(auto it = last+1; itposition, it->position, 0, poly.layer); + last = it; + } + img_line(poly.vertices.front().position, poly.vertices.back().position, 0, poly.layer); + } + + } + + void CanvasGerber::img_line(const Coordi &p0, const Coordi &p1, const uint64_t width, int layer) { + if(GerberWriter *wr = exporter->get_writer_for_layer(layer)) { + wr->draw_line(transform.transform(p0), transform.transform(p1), width); + } + + } + + void CanvasGerber::img_padstack(const Padstack &padstack) { + for(const auto &it: padstack.polygons) { + auto poly = it.second.remove_arcs(32); + if(GerberWriter *wr = exporter->get_writer_for_layer(poly.layer)) { + wr->draw_padstack(padstack.uuid, poly, transform); + } + } + } + + void CanvasGerber::img_hole(const Hole &hole) { + auto wr = exporter->get_drill_writer(hole.plated); + wr->draw_hole(transform.transform(hole.position), hole.diameter); + } + + +} diff --git a/export_gerber/canvas_gerber.hpp b/export_gerber/canvas_gerber.hpp new file mode 100644 index 000000000..f11be7076 --- /dev/null +++ b/export_gerber/canvas_gerber.hpp @@ -0,0 +1,20 @@ +#pragma once +#include "canvas/canvas.hpp" + +namespace horizon { + class CanvasGerber: public Canvas { + public : + CanvasGerber(class GerberExporter *exp); + void push() override {} + void request_push() override; + private : + + void img_net(const Net *net) override; + void img_polygon(const Polygon &poly) override; + void img_line(const Coordi &p0, const Coordi &p1, const uint64_t width, int layer) override; + void img_padstack(const Padstack &ps) override; + void img_hole(const Hole &hole) override; + + GerberExporter *exporter; + }; +} diff --git a/export_gerber/excellon_writer.cpp b/export_gerber/excellon_writer.cpp new file mode 100644 index 000000000..9926ea542 --- /dev/null +++ b/export_gerber/excellon_writer.cpp @@ -0,0 +1,68 @@ +#include "excellon_writer.hpp" +#include + +namespace horizon { + + ExcellonWriter::ExcellonWriter(const std::string &filename): ofs(filename), out_filename(filename) { + check_open(); + } + + const std::string &ExcellonWriter::get_filename() { + return out_filename; + } + + void ExcellonWriter::write_line(const std::string &s) { + check_open(); + ofs << s << std::endl; + } + + void ExcellonWriter::check_open() { + if(!ofs.is_open()) { + throw std::runtime_error("not opened"); + } + } + + void ExcellonWriter::close() { + write_line("M30"); + ofs.close(); + } + + void ExcellonWriter::write_format() { + write_line("M48"); + write_line("FMAT,2"); + write_line("METRIC,TZ"); + } + + void ExcellonWriter::write_header() { + ofs.precision(3); + for(const auto &it: tools) { + ofs << "T" << it.second << "C" << std::fixed << (double)it.first/1e6 << std::endl; + } + write_line("%"); + write_line("G90"); + write_line("G05"); + write_line("M71"); + } + + void ExcellonWriter::draw_hole(const Coordi &pos, uint64_t diameter) { + unsigned int tool; + if(tools.count(diameter)) { + tool = tools.at(diameter); + } + else { + tool = tool_n++; + tools.emplace(diameter, tool); + } + holes.emplace_back(pos, tool); + } + + void ExcellonWriter::write_holes() { + ofs.precision(3); + for(const auto &it: holes) { + ofs << "T" << it.second << std::endl; + ofs << "X" << std::fixed << (double)it.first.x/1e6 << "Y" << std::fixed << (double)it.first.y/1e6 << std::endl; + } + } + + +} diff --git a/export_gerber/excellon_writer.hpp b/export_gerber/excellon_writer.hpp new file mode 100644 index 000000000..46df0496d --- /dev/null +++ b/export_gerber/excellon_writer.hpp @@ -0,0 +1,35 @@ +#pragma once +#include "common.hpp" +#include +#include +#include +#include +#include "padstack.hpp" +#include "placement.hpp" + +namespace horizon { + + class ExcellonWriter { + public: + ExcellonWriter(const std::string &filename); + void write_line(const std::string &s); + void close(); + void write_format(); + void write_header(); + void write_holes(); + void draw_hole(const Coordi &pos, uint64_t diameter); + const std::string &get_filename(); + + + private: + std::map tools; + unsigned int tool_n = 1; + std::deque> holes; + + + std::ofstream ofs; + std::string out_filename; + void check_open(); + + }; +} diff --git a/export_gerber/gerber_export.cpp b/export_gerber/gerber_export.cpp new file mode 100644 index 000000000..96f775ba0 --- /dev/null +++ b/export_gerber/gerber_export.cpp @@ -0,0 +1,61 @@ +#include "gerber_export.hpp" +#include "canvas_gerber.hpp" + +namespace horizon { + GerberExporter::GerberExporter(CoreBoard *c, const CAMJob &j, const std::string &prefix): core(c), job(j) { + + for(const auto &it: job.layers) { + if(core->get_layers().count(it.first)) + writers.emplace(it.first, prefix+it.second); + } + + drill_writer_pth = std::make_unique(prefix+job.drill_pth); + if(!job.merge_npth) { + drill_writer_npth = std::make_unique(prefix+job.drill_npth); + } + } + + void GerberExporter::save() { + CanvasGerber ca(this); + ca.set_core(core); + ca.update(*core->get_board()); + + for(auto &it: writers) { + it.second.write_format(); + it.second.write_apertures(); + it.second.write_lines(); + it.second.write_pads(); + it.second.close(); + log << "Wrote layer "<< core->get_layers().at(it.first).name << " to gerber file " << it.second.get_filename() << std::endl; + } + for(auto it: {drill_writer_npth.get(), drill_writer_pth.get()}) { + if(it) { + it->write_format(); + it->write_header(); + it->write_holes(); + it->close(); + log << "Wrote excellon drill file " << it->get_filename() << std::endl; + } + } + } + + std::string GerberExporter::get_log() { + return log.str(); + } + + GerberWriter *GerberExporter::get_writer_for_layer(int l) { + if(writers.count(l)) { + return &writers.at(l); + } + return nullptr; + } + + ExcellonWriter *GerberExporter::get_drill_writer(bool pth) { + if(job.merge_npth) { + return drill_writer_pth.get(); + } + else { + return pth?drill_writer_pth.get():drill_writer_npth.get(); + } + } +} diff --git a/export_gerber/gerber_export.hpp b/export_gerber/gerber_export.hpp new file mode 100644 index 000000000..225ba960c --- /dev/null +++ b/export_gerber/gerber_export.hpp @@ -0,0 +1,28 @@ +#pragma once +#include "board.hpp" +#include "core/core_board.hpp" +#include "gerber_writer.hpp" +#include "excellon_writer.hpp" +#include "cam_job.hpp" +#include +#include + +namespace horizon { + class GerberExporter { + friend class CanvasGerber; + public : + GerberExporter(CoreBoard *c, const CAMJob &j, const std::string &prefix); + void save(); + std::string get_log(); + + private: + CoreBoard *core; + const CAMJob &job; + std::map writers; + GerberWriter *get_writer_for_layer(int l); + ExcellonWriter *get_drill_writer(bool pth); + std::unique_ptr drill_writer_pth; + std::unique_ptr drill_writer_npth; + std::stringstream log; + }; +} diff --git a/export_gerber/gerber_writer.cpp b/export_gerber/gerber_writer.cpp new file mode 100644 index 000000000..5232609d7 --- /dev/null +++ b/export_gerber/gerber_writer.cpp @@ -0,0 +1,129 @@ +#include "gerber_writer.hpp" +#include + +namespace horizon { + + GerberWriter::GerberWriter(const std::string &filename): ofs(filename), out_filename(filename) { + check_open(); + } + + const std::string &GerberWriter::get_filename() { + return out_filename; + } + + void GerberWriter::write_line(const std::string &s) { + check_open(); + ofs << s << std::endl; + } + + void GerberWriter::check_open() { + if(!ofs.is_open()) { + throw std::runtime_error("not opened"); + } + } + + void GerberWriter::close() { + write_line("M02*"); + ofs.close(); + } + + void GerberWriter::comment(const std::string &s) { + if(s.find('*')!=std::string::npos) { + throw std::runtime_error("comment must not include asterisk"); + } + ofs << "G04 " << s << "*" << std::endl; + } + + void GerberWriter::write_format() { + write_line("%FSLAX46Y46*%"); + write_line("%MOMM*%"); + } + + unsigned int GerberWriter::get_or_create_aperture_circle(uint64_t diameter) { + if(apertures_circle.count(diameter)) { + return apertures_circle.at(diameter); + } + else { + auto n = aperture_n++; + apertures_circle.emplace(diameter, n); + return n; + } + } + + void GerberWriter::write_apertures() { + ofs.precision(6); + for(const auto &it: apertures_circle) { + ofs << "%ADD" << it.second << "C," << std::fixed << (double)it.first/1e6 << "*%" << std::endl; + } + + /*%AMOUTLINE* + 4,1,3, + 0.1,0.2, + 0.5,0.1, + 0.5,0.5, + 0.1,0.5, + 0.1,0.1, + 0*% + %ADD18OUTLINE*% + */ + + for(const auto &it: apertures_polygon) { + ofs << "%AMPS" << it.second.name << "*" << std::endl; + ofs << "4,1," << it.second.vertices.size() << "," << std::endl; + + auto write_vertex = [this](const Coordi &c){ofs << std::fixed << (double)c.x/1e6 << "," << (double)c.y/1e6 << "," << std::endl;}; + for(const auto &v: it.second.vertices) { + write_vertex(v); + } + write_vertex(it.second.vertices.front()); + + ofs << "0*%" << std::endl; + ofs << "%ADD" << it.second.name << "PS" << it.second.name << "*%"<< std::endl; + } + } + + std::ostream &operator<<(std::ostream &os, const Coordi &c) { + return os << "X" << c.x << "Y" << c.y; + } + + void GerberWriter::write_lines() { + write_line("*G01"); + write_line("%LPD*%"); + for(const auto &it: lines) { + ofs << "D" << it.aperture << "*" << std::endl; + ofs << it.from << "D02*" << std::endl; + ofs << it.to << "D01*" << std::endl; + } + } + + void GerberWriter::write_pads() { + for(const auto &it: pads) { + ofs << "D" << it.first << "*" << std::endl; + ofs << it.second << "D03*" << std::endl; + } + } + + void GerberWriter::draw_line(const Coordi &from, const Coordi &to, uint64_t width) { + auto ap = get_or_create_aperture_circle(width); + lines.emplace_back(from, to, ap); + } + + void GerberWriter::draw_padstack(const UUID &padstack_uuid, const Polygon &poly, const Placement &transform) { + auto key = std::make_tuple(padstack_uuid, transform.angle, transform.mirror); + GerberWriter::PolygonAperture *ap = nullptr; + if(apertures_polygon.count(key)) { + ap = &apertures_polygon.at(key); + } + else { + auto n = aperture_n++; + ap = &apertures_polygon.emplace(key, n).first->second; + ap->vertices.reserve(poly.vertices.size()); + auto tr = transform; + tr.shift = Coordi(); + for(const auto &it: poly.vertices) { + ap->vertices.push_back(tr.transform(it.position)); + } + } + pads.emplace_back(ap->name, transform.shift); + } +} diff --git a/export_gerber/gerber_writer.hpp b/export_gerber/gerber_writer.hpp new file mode 100644 index 000000000..804e0cb9f --- /dev/null +++ b/export_gerber/gerber_writer.hpp @@ -0,0 +1,57 @@ +#pragma once +#include "common.hpp" +#include +#include +#include +#include +#include "padstack.hpp" +#include "placement.hpp" + +namespace horizon { + + class GerberWriter { + public: + GerberWriter(const std::string &filename); + void write_line(const std::string &s); + void close(); + void comment(const std::string &s); + void write_format(); + void write_apertures(); + void write_lines(); + void write_pads(); + unsigned int get_or_create_aperture_circle(uint64_t diameter); + //unsigned int get_or_create_aperture_padstack(const class Padstack *ps, int layer, ) + void draw_line(const Coordi &from, const Coordi &to, uint64_t width); + void draw_padstack(const UUID &padstack_uuid, const Polygon &poly, const Placement &transform); + const std::string &get_filename(); + + private: + class Line { + public: + Line(const Coordi &f, const Coordi &t, unsigned int ap): from(f), to(t), aperture(ap) {} + Coordi from; + Coordi to; + unsigned int aperture; + }; + + class PolygonAperture { + public: + PolygonAperture(unsigned int n):name(n){} + + unsigned int name; + std::vector vertices; + }; + + std::ofstream ofs; + std::string out_filename; + void check_open(); + std::map apertures_circle; + std::map, PolygonAperture> apertures_polygon; + + unsigned int aperture_n=10; + + std::deque lines; + std::deque> pads; + void write_coord(const Coordi &c); + }; +} diff --git a/export_pdf.cpp b/export_pdf.cpp new file mode 100644 index 000000000..ea6b757e5 --- /dev/null +++ b/export_pdf.cpp @@ -0,0 +1,24 @@ +#include "export_pdf.hpp" +#include "canvas/canvas_cairo.hpp" + +namespace horizon { + + + void export_pdf(const std::string &filename, const Schematic &sch, Core *c) { + int width = 842; + int height = 595; + Cairo::RefPtr surface = Cairo::PdfSurface::create(filename, width, height); + + Cairo::RefPtr cr = Cairo::Context::create(surface); + CanvasCairo ca(cr); + ca.set_core(c); + for(const auto &it: sch.sheets) { + ca.update(it.second); + + cr->show_page(); + } + + } + + +} diff --git a/export_pdf.hpp b/export_pdf.hpp new file mode 100644 index 000000000..4889a864a --- /dev/null +++ b/export_pdf.hpp @@ -0,0 +1,7 @@ +#pragma once +#include "schematic.hpp" +#include "core/core.hpp" + +namespace horizon { + void export_pdf(const std::string &filename, const Schematic &sch, Core *c); +} diff --git a/imp.gresource.xml b/imp.gresource.xml new file mode 100644 index 000000000..1f995de61 --- /dev/null +++ b/imp.gresource.xml @@ -0,0 +1,33 @@ + + + + window.ui + property_panels/property_panel.ui + imp/cam_job.ui + pool-util/part-editor.ui + pool-update/schema.sql + pool-update-parametric/schema.sql + dialogs/pool_browser.ui + dialogs/pool_browser_part.ui + + + imp/footprint_generator/footprint_generator.ui + imp/footprint_generator/dual.svg + imp/footprint_generator/single.svg + imp/footprint_generator/quad.svg + + canvas/shaders/grid-vertex.glsl + canvas/shaders/grid-fragment.glsl + + canvas/shaders/selectable-vertex.glsl + canvas/shaders/selectable-fragment.glsl + canvas/shaders/selectable-geometry.glsl + + canvas/shaders/selection-vertex.glsl + canvas/shaders/selection-fragment.glsl + + canvas/shaders/triangle-vertex.glsl + canvas/shaders/triangle-fragment.glsl + canvas/shaders/triangle-geometry.glsl + + diff --git a/imp/cam_job.ui b/imp/cam_job.ui new file mode 100644 index 000000000..021650ad6 --- /dev/null +++ b/imp/cam_job.ui @@ -0,0 +1,182 @@ + + + + + + False + True + dialog + + + True + False + 10 + 10 + 10 + 10 + vertical + 16 + + + True + False + 10 + 10 + + + True + False + end + Job file + + + + 0 + 0 + + + + + True + False + end + Output directory + + + + 0 + 2 + + + + + True + False + end + Prefix + + + + 0 + 3 + + + + + True + False + True + Choose job file + + + 1 + 0 + + + + + True + False + select-folder + Choose output directory + + + 1 + 2 + + + + + True + True + + + 1 + 3 + + + + + True + False + end + Job title + + + + 0 + 1 + + + + + True + False + start + not loaded + + + 1 + 1 + + + + + False + True + 0 + + + + + True + True + never + in + 70 + + + True + True + False + + + + + True + True + 1 + + + + + + + True + False + CAM Job + True + + + Run + True + False + True + True + + + + + + + diff --git a/imp/cam_job_dialog.cpp b/imp/cam_job_dialog.cpp new file mode 100644 index 000000000..53b3f8753 --- /dev/null +++ b/imp/cam_job_dialog.cpp @@ -0,0 +1,61 @@ +#include "cam_job_dialog.hpp" +#include "export_gerber/gerber_export.hpp" + +namespace horizon { + + + + CAMJobWindow::CAMJobWindow(BaseObjectType* cobject, const Glib::RefPtr& x) : + Gtk::Window(cobject) { + } + + CAMJobWindow* CAMJobWindow::create(Gtk::Window *p, CoreBoard *c) { + CAMJobWindow* w; + Glib::RefPtr x = Gtk::Builder::create(); + x->add_from_resource("/net/carrotIndustries/horizon/imp/cam_job.ui"); + x->get_widget_derived("window", w); + x->get_widget("job_file", w->w_job_file); + x->get_widget("output_dir", w->w_output_dir); + x->get_widget("prefix", w->w_prefix); + x->get_widget("output", w->w_output); + x->get_widget("job_title", w->w_job_title); + x->get_widget("run_button", w->w_run_button); + + w->set_transient_for(*p); + w->core = c; + w->w_job_file->signal_file_set().connect(sigc::mem_fun(w, &CAMJobWindow::try_load_job_file)); + w->w_output_dir->signal_file_set().connect(sigc::mem_fun(w, &CAMJobWindow::update_can_run)); + w->w_run_button->signal_clicked().connect(sigc::mem_fun(w, &CAMJobWindow::run_job)); + + + return w; + } + + void CAMJobWindow::try_load_job_file() { + std::string job_filename = w_job_file->get_filename(); + try { + job = CAMJob::new_from_file(job_filename); + w_job_title->set_text(job->title); + w_run_button->set_sensitive(true); + } + catch (const std::exception& e) { + w_job_title->set_text("Load error: "+std::string(e.what())); + w_run_button->set_sensitive(false); + job = {}; + } + update_can_run(); + } + + void CAMJobWindow::update_can_run() { + w_run_button->set_sensitive(job && w_output_dir->get_filename().size()>0); + } + + void CAMJobWindow::run_job() { + if(!job) + return; + std::string prefix = w_output_dir->get_filename()+"/"+w_prefix->get_text(); + GerberExporter ex(core, *job, prefix); + ex.save(); + w_output->get_buffer()->set_text(ex.get_log()); + } +} diff --git a/imp/cam_job_dialog.hpp b/imp/cam_job_dialog.hpp new file mode 100644 index 000000000..209b3dc1d --- /dev/null +++ b/imp/cam_job_dialog.hpp @@ -0,0 +1,32 @@ +#pragma once +#include +#include +#include +#include "common.hpp" +#include "uuid.hpp" +#include "core/core_board.hpp" +#include "export_gerber/cam_job.hpp" +#include +namespace horizon { + + class CAMJobWindow: public Gtk::Window{ + public: + CAMJobWindow(BaseObjectType* cobject, const Glib::RefPtr& x); + static CAMJobWindow* create(Gtk::Window *p, CoreBoard *c); + private : + CoreBoard *core; + + Gtk::FileChooserButton *w_job_file = nullptr; + Gtk::FileChooserButton *w_output_dir = nullptr; + Gtk::Entry *w_prefix = nullptr; + Gtk::TextView *w_output = nullptr; + Gtk::Label *w_job_title = nullptr; + Gtk::Button *w_run_button= nullptr; + + + std::experimental::optional job; + void try_load_job_file(); + void update_can_run(); + void run_job(); + }; +} diff --git a/imp/footprint_generator/dual.svg b/imp/footprint_generator/dual.svg new file mode 100644 index 000000000..3007c6f7a --- /dev/null +++ b/imp/footprint_generator/dual.svg @@ -0,0 +1,474 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/imp/footprint_generator/footprint_generator.ui b/imp/footprint_generator/footprint_generator.ui new file mode 100644 index 000000000..84e10f258 --- /dev/null +++ b/imp/footprint_generator/footprint_generator.ui @@ -0,0 +1,47 @@ + + + + + + False + True + dialog + + + True + False + 100 + crossfade + + + + + + + + True + False + True + + + Generate + True + False + True + True + + + + + + True + False + stack + + + + + + diff --git a/imp/footprint_generator/footprint_generator_base.cpp b/imp/footprint_generator/footprint_generator_base.cpp new file mode 100644 index 000000000..59daab9af --- /dev/null +++ b/imp/footprint_generator/footprint_generator_base.cpp @@ -0,0 +1,29 @@ +#include "footprint_generator_base.hpp" +namespace horizon { + FootprintGeneratorBase::FootprintGeneratorBase(const char *resource, CorePackage *c) :Glib::ObjectBase (typeid(FootprintGeneratorBase)), Gtk::Box(Gtk::ORIENTATION_VERTICAL, 4), p_property_can_generate(*this, "can-generate"), core(c) + { + box_top = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 8)); + pack_start(*box_top, false, false, 0); + { + auto tbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4)); + auto la = Gtk::manage(new Gtk::Label("Padstack:")); + tbox->pack_start(*la, false, false, 0); + + padstack_button = Gtk::manage(new PadstackButton(*core->m_pool, core->get_package()->uuid)); + padstack_button->property_selected_uuid().signal_changed().connect([this]{p_property_can_generate = padstack_button->property_selected_uuid()!=UUID();}); + tbox->pack_start(*padstack_button, false, false, 0); + + box_top->pack_start(*tbox, false, false, 0); + } + + + box_top->show_all(); + box_top->set_margin_top(4); + box_top->set_margin_start(4); + box_top->set_margin_end(4); + + overlay = Gtk::manage(new SVGOverlay(resource)); + pack_start(*overlay, true, true, 0); + overlay->show(); + } +} diff --git a/imp/footprint_generator/footprint_generator_base.hpp b/imp/footprint_generator/footprint_generator_base.hpp new file mode 100644 index 000000000..020584ab4 --- /dev/null +++ b/imp/footprint_generator/footprint_generator_base.hpp @@ -0,0 +1,27 @@ +#pragma once +#include +#include +#include +#include "common.hpp" +#include "uuid.hpp" +#include "core/core_package.hpp" +#include "widgets/chooser_buttons.hpp" +#include "svg_overlay.hpp" +namespace horizon { + class FootprintGeneratorBase: public Gtk::Box { + public: + FootprintGeneratorBase(const char *resource, CorePackage *c); + Glib::PropertyProxy property_can_generate() { return p_property_can_generate.get_proxy(); } + + virtual bool generate() = 0; + + protected : + Glib::Property p_property_can_generate; + PadstackButton *padstack_button = nullptr; + + SVGOverlay *overlay = nullptr; + Gtk::Box *box_top = nullptr; + CorePackage *core; + }; + +} diff --git a/imp/footprint_generator/footprint_generator_dual.cpp b/imp/footprint_generator/footprint_generator_dual.cpp new file mode 100644 index 000000000..91fce9b84 --- /dev/null +++ b/imp/footprint_generator/footprint_generator_dual.cpp @@ -0,0 +1,198 @@ +#include "footprint_generator_dual.hpp" + +namespace horizon { + FootprintGeneratorDual::FootprintGeneratorDual(CorePackage *c): Glib::ObjectBase (typeid(FootprintGeneratorDual)), FootprintGeneratorBase("/net/carrotIndustries/horizon/imp/footprint_generator/dual.svg", c) { + + + { + auto tbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4)); + auto la = Gtk::manage(new Gtk::Label("Count:")); + tbox->pack_start(*la, false, false, 0); + + sp_count = Gtk::manage(new Gtk::SpinButton()); + sp_count->set_range(2, 512); + sp_count->set_increments(2, 2); + tbox->pack_start(*sp_count, false, false, 0); + + box_top->pack_start(*tbox, false, false, 0); + } + + update_preview(); + sp_spacing = Gtk::manage(new SpinButtonDim()); + sp_spacing->set_range(0, 100_mm); + sp_spacing->set_valign(Gtk::ALIGN_CENTER); + sp_spacing->set_halign(Gtk::ALIGN_START); + sp_spacing->set_value(3_mm); + overlay->add_at_sub(*sp_spacing, "#spacing"); + sp_spacing->show(); + + sp_pitch = Gtk::manage(new SpinButtonDim()); + sp_pitch->set_range(0, 50_mm); + sp_pitch->set_valign(Gtk::ALIGN_CENTER); + sp_pitch->set_halign(Gtk::ALIGN_START); + sp_pitch->set_value(1_mm); + overlay->add_at_sub(*sp_pitch, "#pitch"); + sp_pitch->show(); + + sp_count->signal_value_changed().connect([this]{pad_count = sp_count->get_value_as_int(); update_preview();}); + + { + auto tbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4)); + auto la = Gtk::manage(new Gtk::Label("Mode:")); + tbox->pack_start(*la, false, false, 0); + + auto rbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0)); + rbox->get_style_context()->add_class("linked"); + auto rb1 = Gtk::manage(new Gtk::RadioButton("Circular")); + rb1->set_mode(false); + auto rb2 = Gtk::manage(new Gtk::RadioButton("Zigzag")); + rb2->signal_toggled().connect([this, rb2]{zigzag = rb2->get_active(); update_preview();}); + //sp_count->signal_changed().connect([this]{set_pad_count(sp_count->get_value_as_int());}); + rb2->set_mode(false); + rb2->join_group(*rb1); + rbox->pack_start(*rb1, false, false, 0); + rbox->pack_start(*rb2, false, false, 0); + rb1->set_active(true); + + + tbox->pack_start(*rbox, false, false, 0); + + + box_top->pack_start(*tbox, false, false, 0); + } + sp_count->set_value(4); + + } + + bool FootprintGeneratorDual::generate() { + if(!property_can_generate()) + return false; + auto pkg = core->get_package(); + int64_t spacing = sp_spacing->get_value_as_int(); + int64_t pitch = sp_pitch->get_value_as_int(); + int64_t y0 = (pad_count/2-1)*(pitch/2); + for(auto it: {-1, 1}) { + for(unsigned int i = 0; im_pool->get_padstack(padstack_button->property_selected_uuid()); + auto &pad = pkg->pads.emplace(uu, Pad(uu, padstack)).first->second; + pad.placement.shift = {it*spacing, y0-pitch*i}; + if(it < 0) + pad.placement.angle = 49152; + else + pad.placement.angle = 16384; + if(!zigzag) { + if(it < 0) { + pad.name = std::to_string(i+1); + } + else { + pad.name = std::to_string(pad_count-i); + } + } + else { + if(it < 0) { + pad.name = std::to_string(i*2+1); + } + else { + pad.name = std::to_string(i*2+2); + } + } + } + } + + core->commit(); + return true; + } + + void FootprintGeneratorDual::update_preview() { + auto n = pad_count; + n &= ~1; + if(n<2) + return; + if(!zigzag) { + if(n>=8) { + overlay->sub_texts["#pad1"] = "1"; + overlay->sub_texts["#pad2"] = "2"; + overlay->sub_texts["#pad3"] = std::to_string(n/2-1); + overlay->sub_texts["#pad4"] = std::to_string(n/2); + overlay->sub_texts["#pad5"] = std::to_string(n/2+1); + overlay->sub_texts["#pad6"] = std::to_string(n/2+2); + overlay->sub_texts["#pad7"] = std::to_string(n-1); + overlay->sub_texts["#pad8"] = std::to_string(n); + } + else if(n==6) { + overlay->sub_texts["#pad1"] = "1"; + overlay->sub_texts["#pad2"] = "2"; + overlay->sub_texts["#pad3"] = "3"; + overlay->sub_texts["#pad4"] = "X"; + overlay->sub_texts["#pad5"] = "X"; + overlay->sub_texts["#pad6"] = "4"; + overlay->sub_texts["#pad7"] = "5"; + overlay->sub_texts["#pad8"] = "6"; + } + else if(n==4) { + overlay->sub_texts["#pad1"] = "1"; + overlay->sub_texts["#pad2"] = "2"; + overlay->sub_texts["#pad3"] = "X"; + overlay->sub_texts["#pad4"] = "X"; + overlay->sub_texts["#pad5"] = "X"; + overlay->sub_texts["#pad6"] = "X"; + overlay->sub_texts["#pad7"] = "3"; + overlay->sub_texts["#pad8"] = "4"; + } + else if(n==2) { + overlay->sub_texts["#pad1"] = "1"; + overlay->sub_texts["#pad2"] = "X"; + overlay->sub_texts["#pad3"] = "X"; + overlay->sub_texts["#pad4"] = "X"; + overlay->sub_texts["#pad5"] = "X"; + overlay->sub_texts["#pad6"] = "X"; + overlay->sub_texts["#pad7"] = "X"; + overlay->sub_texts["#pad8"] = "2"; + } + } + else { + if(n>=8) { + overlay->sub_texts["#pad1"] = "1"; + overlay->sub_texts["#pad8"] = "2"; + overlay->sub_texts["#pad2"] = "3"; + overlay->sub_texts["#pad7"] = "4"; + overlay->sub_texts["#pad5"] = std::to_string(n); + overlay->sub_texts["#pad4"] = std::to_string(n-1); + overlay->sub_texts["#pad6"] = std::to_string(n-2); + overlay->sub_texts["#pad3"] = std::to_string(n-3); + } + else if(n==6) { + overlay->sub_texts["#pad1"] = "1"; + overlay->sub_texts["#pad8"] = "2"; + overlay->sub_texts["#pad2"] = "3"; + overlay->sub_texts["#pad7"] = "4"; + overlay->sub_texts["#pad5"] = "X"; + overlay->sub_texts["#pad4"] = "X"; + overlay->sub_texts["#pad6"] = "6"; + overlay->sub_texts["#pad3"] = "5"; + } + else if(n==4) { + overlay->sub_texts["#pad1"] = "1"; + overlay->sub_texts["#pad8"] = "2"; + overlay->sub_texts["#pad2"] = "3"; + overlay->sub_texts["#pad7"] = "4"; + overlay->sub_texts["#pad5"] = "X"; + overlay->sub_texts["#pad4"] = "X"; + overlay->sub_texts["#pad6"] = "X"; + overlay->sub_texts["#pad3"] = "X"; + } + else if(n==2) { + overlay->sub_texts["#pad1"] = "1"; + overlay->sub_texts["#pad8"] = "2"; + overlay->sub_texts["#pad2"] = "X"; + overlay->sub_texts["#pad7"] = "X"; + overlay->sub_texts["#pad5"] = "X"; + overlay->sub_texts["#pad4"] = "X"; + overlay->sub_texts["#pad6"] = "X"; + overlay->sub_texts["#pad3"] = "X"; + } + } + overlay->queue_draw(); + } +} diff --git a/imp/footprint_generator/footprint_generator_dual.hpp b/imp/footprint_generator/footprint_generator_dual.hpp new file mode 100644 index 000000000..174b413f2 --- /dev/null +++ b/imp/footprint_generator/footprint_generator_dual.hpp @@ -0,0 +1,18 @@ +#pragma once +#include "footprint_generator_base.hpp" +#include "widgets/spin_button_dim.hpp" +namespace horizon { + class FootprintGeneratorDual: public FootprintGeneratorBase { + public: + FootprintGeneratorDual(CorePackage *c); + bool generate() override; + + private: + Gtk::SpinButton *sp_count = nullptr; + SpinButtonDim *sp_spacing = nullptr; + SpinButtonDim *sp_pitch= nullptr; + unsigned int pad_count = 4; + bool zigzag = false; + void update_preview(); + }; +} diff --git a/imp/footprint_generator/footprint_generator_quad.cpp b/imp/footprint_generator/footprint_generator_quad.cpp new file mode 100644 index 000000000..5e64311aa --- /dev/null +++ b/imp/footprint_generator/footprint_generator_quad.cpp @@ -0,0 +1,202 @@ +#include "footprint_generator_quad.hpp" + +namespace horizon { + FootprintGeneratorQuad::FootprintGeneratorQuad(CorePackage *c): Glib::ObjectBase (typeid(FootprintGeneratorQuad)), FootprintGeneratorBase("/net/carrotIndustries/horizon/imp/footprint_generator/quad.svg", c) { + update_preview(); + sp_spacing_h = Gtk::manage(new SpinButtonDim()); + sp_spacing_h->set_range(0, 100_mm); + sp_spacing_h->set_valign(Gtk::ALIGN_CENTER); + sp_spacing_h->set_halign(Gtk::ALIGN_START); + sp_spacing_h->set_value(40_mm); + overlay->add_at_sub(*sp_spacing_h, "#spacing_h"); + sp_spacing_h->show(); + + sp_spacing_v = Gtk::manage(new SpinButtonDim()); + sp_spacing_v->set_range(0, 100_mm); + sp_spacing_v->set_valign(Gtk::ALIGN_CENTER); + sp_spacing_v->set_valign(Gtk::ALIGN_START); + sp_spacing_v->set_value(30_mm); + overlay->add_at_sub(*sp_spacing_v, "#spacing_v"); + sp_spacing_v->show(); + + sp_pitch = Gtk::manage(new SpinButtonDim()); + sp_pitch->set_range(0, 50_mm); + sp_pitch->set_valign(Gtk::ALIGN_CENTER); + sp_pitch->set_halign(Gtk::ALIGN_START); + sp_pitch->set_value(3_mm); + overlay->add_at_sub(*sp_pitch, "#pitch"); + sp_pitch->show(); + + { + auto tbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4)); + auto la = Gtk::manage(new Gtk::Label("Count H:")); + tbox->pack_start(*la, false, false, 0); + + sp_count_h = Gtk::manage(new Gtk::SpinButton()); + sp_count_h->set_range(1, 512); + sp_count_h->set_increments(1, 1); + tbox->pack_start(*sp_count_h, false, false, 0); + + box_top->pack_start(*tbox, false, false, 0); + } + { + auto tbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4)); + auto la = Gtk::manage(new Gtk::Label("Count V:")); + tbox->pack_start(*la, false, false, 0); + + sp_count_v = Gtk::manage(new Gtk::SpinButton()); + sp_count_v->set_range(1, 512); + sp_count_v->set_increments(1, 1); + tbox->pack_start(*sp_count_v, false, false, 0); + + box_top->pack_start(*tbox, false, false, 0); + } + + sp_count_h->signal_value_changed().connect([this]{pad_count_h = sp_count_h->get_value_as_int(); update_preview();}); + sp_count_v->signal_value_changed().connect([this]{pad_count_v = sp_count_v->get_value_as_int(); update_preview();}); + + sp_count_h->set_value(4); + sp_count_v->set_value(4); + + } + + bool FootprintGeneratorQuad::generate() { + if(!property_can_generate()) + return false; + auto pkg = core->get_package(); + int64_t pitch = sp_pitch->get_value_as_int(); + int64_t spacing_h = sp_spacing_h->get_value_as_int(); + int64_t y0 = (pad_count_v-1)*(pitch/2); + for(auto it: {-1, 1}) { + for(unsigned int i = 0; im_pool->get_padstack(padstack_button->property_selected_uuid()); + auto &pad = pkg->pads.emplace(uu, Pad(uu, padstack)).first->second; + pad.placement.shift = {it*spacing_h, y0-pitch*i}; + if(it < 0) { + pad.placement.angle = 49152; + pad.name = std::to_string(i+1); + } + else { + pad.placement.angle = 16384; + pad.name = std::to_string(pad_count_v*2+pad_count_h-i); + } + } + } + + int64_t spacing_v = sp_spacing_v->get_value_as_int(); + int64_t x0 = -1L*(pad_count_h-1)*(pitch/2); + for(auto it: {-1, 1}) { + for(unsigned int i = 0; im_pool->get_padstack(padstack_button->property_selected_uuid()); + auto &pad = pkg->pads.emplace(uu, Pad(uu, padstack)).first->second; + pad.placement.shift = {x0+pitch*i, it*spacing_v}; + if(it < 0) { + pad.placement.angle = 0; + pad.name = std::to_string(i+1+pad_count_v); + } + else { + pad.placement.angle = 32768; + pad.name = std::to_string(pad_count_v*2+pad_count_h*2-i); + } + } + } + core->commit(); + return true; + } + + void FootprintGeneratorQuad::update_preview() { + if(pad_count_v >= 4) { + overlay->sub_texts["#pad1"] = "1"; + overlay->sub_texts["#pad2"] = "2"; + overlay->sub_texts["#pad3"] = std::to_string(pad_count_v-1); + overlay->sub_texts["#pad4"] = std::to_string(pad_count_v); + + overlay->sub_texts["#pad9"] = std::to_string(pad_count_h+pad_count_v+1); + overlay->sub_texts["#pad10"] = std::to_string(pad_count_h+pad_count_v+2); + overlay->sub_texts["#pad11"] = std::to_string(pad_count_h+pad_count_v*2-1); + overlay->sub_texts["#pad12"] = std::to_string(pad_count_h+pad_count_v*2); + } + else if(pad_count_v == 3) { + overlay->sub_texts["#pad1"] = "1"; + overlay->sub_texts["#pad2"] = "2"; + overlay->sub_texts["#pad3"] = "X"; + overlay->sub_texts["#pad4"] = "3"; + + overlay->sub_texts["#pad9"] = std::to_string(pad_count_h+pad_count_v+1); + overlay->sub_texts["#pad10"] = std::to_string(pad_count_h+pad_count_v+2); + overlay->sub_texts["#pad11"] = "X"; + overlay->sub_texts["#pad12"] = std::to_string(pad_count_h+pad_count_v*2); + } + else if(pad_count_v == 2) { + overlay->sub_texts["#pad1"] = "1"; + overlay->sub_texts["#pad2"] = "X"; + overlay->sub_texts["#pad3"] = "X"; + overlay->sub_texts["#pad4"] = "2"; + + overlay->sub_texts["#pad9"] = std::to_string(pad_count_h+pad_count_v+1); + overlay->sub_texts["#pad10"] = "X"; + overlay->sub_texts["#pad11"] = "X"; + overlay->sub_texts["#pad12"] = std::to_string(pad_count_h+pad_count_v*2); + } + else if(pad_count_v == 1) { + overlay->sub_texts["#pad1"] = "1"; + overlay->sub_texts["#pad2"] = "X"; + overlay->sub_texts["#pad3"] = "X"; + overlay->sub_texts["#pad4"] = "X"; + + overlay->sub_texts["#pad9"] = "X"; + overlay->sub_texts["#pad10"] = "X"; + overlay->sub_texts["#pad11"] = "X"; + overlay->sub_texts["#pad12"] = std::to_string(pad_count_h+pad_count_v*2); + } + + if(pad_count_h >= 4) { + overlay->sub_texts["#pad5"] = std::to_string(pad_count_v+1); + overlay->sub_texts["#pad6"] = std::to_string(pad_count_v+2); + overlay->sub_texts["#pad7"] = std::to_string(pad_count_v+pad_count_h-1); + overlay->sub_texts["#pad8"] = std::to_string(pad_count_v+pad_count_h); + + overlay->sub_texts["#pad13"] = std::to_string(pad_count_v*2+pad_count_h+1); + overlay->sub_texts["#pad14"] = std::to_string(pad_count_v*2+pad_count_h+2); + overlay->sub_texts["#pad15"] = std::to_string(pad_count_v*2+pad_count_h*2-1); + overlay->sub_texts["#pad16"] = std::to_string(pad_count_v*2+pad_count_h*2); + } + else if(pad_count_h == 3) { + overlay->sub_texts["#pad5"] = std::to_string(pad_count_v+1); + overlay->sub_texts["#pad6"] = std::to_string(pad_count_v+2); + overlay->sub_texts["#pad7"] = "X"; + overlay->sub_texts["#pad8"] = std::to_string(pad_count_v+pad_count_h); + + overlay->sub_texts["#pad13"] = std::to_string(pad_count_v*2+pad_count_h+1); + overlay->sub_texts["#pad14"] = std::to_string(pad_count_v*2+pad_count_h+2); + overlay->sub_texts["#pad15"] = "X"; + overlay->sub_texts["#pad16"] = std::to_string(pad_count_v*2+pad_count_h*2); + } + else if(pad_count_h == 2) { + overlay->sub_texts["#pad5"] = std::to_string(pad_count_v+1); + overlay->sub_texts["#pad6"] = "X"; + overlay->sub_texts["#pad7"] = "X"; + overlay->sub_texts["#pad8"] = std::to_string(pad_count_v+pad_count_h); + + overlay->sub_texts["#pad13"] = std::to_string(pad_count_v*2+pad_count_h+1); + overlay->sub_texts["#pad14"] = "X"; + overlay->sub_texts["#pad15"] = "X"; + overlay->sub_texts["#pad16"] = std::to_string(pad_count_v*2+pad_count_h*2); + } + else if(pad_count_h == 1) { + overlay->sub_texts["#pad5"] = std::to_string(pad_count_v+1); + overlay->sub_texts["#pad6"] = "X"; + overlay->sub_texts["#pad7"] = "X"; + overlay->sub_texts["#pad8"] = "X"; + + overlay->sub_texts["#pad13"] = "X"; + overlay->sub_texts["#pad14"] = "X"; + overlay->sub_texts["#pad15"] = "X"; + overlay->sub_texts["#pad16"] = std::to_string(pad_count_v*2+pad_count_h*2); + } + + overlay->queue_draw(); + } +} diff --git a/imp/footprint_generator/footprint_generator_quad.hpp b/imp/footprint_generator/footprint_generator_quad.hpp new file mode 100644 index 000000000..2d1b2ad9f --- /dev/null +++ b/imp/footprint_generator/footprint_generator_quad.hpp @@ -0,0 +1,20 @@ +#pragma once +#include "footprint_generator_base.hpp" +#include "widgets/spin_button_dim.hpp" +namespace horizon { + class FootprintGeneratorQuad: public FootprintGeneratorBase { + public: + FootprintGeneratorQuad(CorePackage *c); + bool generate() override; + + private: + Gtk::SpinButton *sp_count_h = nullptr; + Gtk::SpinButton *sp_count_v = nullptr; + SpinButtonDim *sp_spacing_h = nullptr; + SpinButtonDim *sp_spacing_v = nullptr; + SpinButtonDim *sp_pitch= nullptr; + unsigned int pad_count_h = 4; + unsigned int pad_count_v = 4; + void update_preview(); + }; +} diff --git a/imp/footprint_generator/footprint_generator_single.cpp b/imp/footprint_generator/footprint_generator_single.cpp new file mode 100644 index 000000000..3f5577a92 --- /dev/null +++ b/imp/footprint_generator/footprint_generator_single.cpp @@ -0,0 +1,83 @@ +#include "footprint_generator_single.hpp" + +namespace horizon { + FootprintGeneratorSingle::FootprintGeneratorSingle(CorePackage *c): Glib::ObjectBase (typeid(FootprintGeneratorSingle)), FootprintGeneratorBase("/net/carrotIndustries/horizon/imp/footprint_generator/single.svg", c) { + + { + auto tbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4)); + auto la = Gtk::manage(new Gtk::Label("Count:")); + tbox->pack_start(*la, false, false, 0); + + sp_count = Gtk::manage(new Gtk::SpinButton()); + sp_count->set_range(2, 512); + sp_count->set_increments(2, 2); + tbox->pack_start(*sp_count, false, false, 0); + + box_top->pack_start(*tbox, false, false, 0); + } + + update_preview(); + + sp_pitch = Gtk::manage(new SpinButtonDim()); + sp_pitch->set_range(0, 50_mm); + sp_pitch->set_valign(Gtk::ALIGN_CENTER); + sp_pitch->set_halign(Gtk::ALIGN_START); + sp_pitch->set_value(1_mm); + overlay->add_at_sub(*sp_pitch, "#pitch"); + sp_pitch->show(); + + sp_count->signal_value_changed().connect([this]{pad_count = sp_count->get_value_as_int(); update_preview();}); + sp_count->set_value(4); + sp_count->set_range(1, 512); + sp_count->set_increments(1, 1); + + } + + bool FootprintGeneratorSingle::generate() { + if(!property_can_generate()) + return false; + auto pkg = core->get_package(); + int64_t pitch = sp_pitch->get_value_as_int(); + int64_t y0 = (pad_count-1)*(pitch/2); + for(unsigned int i = 0; im_pool->get_padstack(padstack_button->property_selected_uuid()); + auto &pad = pkg->pads.emplace(uu, Pad(uu, padstack)).first->second; + pad.placement.shift = {0, y0-pitch*i}; + pad.placement.angle = 49152; + pad.name = std::to_string(i+1); + } + + core->commit(); + return true; + } + + void FootprintGeneratorSingle::update_preview() { + auto n = pad_count; + if(n>=4) { + overlay->sub_texts["#pad1"] = "1"; + overlay->sub_texts["#pad2"] = "2"; + overlay->sub_texts["#pad3"] = std::to_string(n-1); + overlay->sub_texts["#pad4"] = std::to_string(n); + } + else if(n==3) { + overlay->sub_texts["#pad1"] = "1"; + overlay->sub_texts["#pad2"] = "2"; + overlay->sub_texts["#pad3"] = "3"; + overlay->sub_texts["#pad4"] = "X"; + } + else if(n==2) { + overlay->sub_texts["#pad1"] = "1"; + overlay->sub_texts["#pad2"] = "2"; + overlay->sub_texts["#pad3"] = "X"; + overlay->sub_texts["#pad4"] = "X"; + } + else if(n==1) { + overlay->sub_texts["#pad1"] = "1"; + overlay->sub_texts["#pad2"] = "X"; + overlay->sub_texts["#pad3"] = "X"; + overlay->sub_texts["#pad4"] = "X"; + } + overlay->queue_draw(); + } +} diff --git a/imp/footprint_generator/footprint_generator_single.hpp b/imp/footprint_generator/footprint_generator_single.hpp new file mode 100644 index 000000000..04fcdbfb9 --- /dev/null +++ b/imp/footprint_generator/footprint_generator_single.hpp @@ -0,0 +1,16 @@ +#pragma once +#include "footprint_generator_base.hpp" +#include "widgets/spin_button_dim.hpp" +namespace horizon { + class FootprintGeneratorSingle: public FootprintGeneratorBase { + public: + FootprintGeneratorSingle(CorePackage *c); + bool generate() override; + + private: + Gtk::SpinButton *sp_count = nullptr; + SpinButtonDim *sp_pitch= nullptr; + unsigned int pad_count = 4; + void update_preview(); + }; +} diff --git a/imp/footprint_generator/footprint_generator_window.cpp b/imp/footprint_generator/footprint_generator_window.cpp new file mode 100644 index 000000000..969f21ec1 --- /dev/null +++ b/imp/footprint_generator/footprint_generator_window.cpp @@ -0,0 +1,66 @@ +#include "footprint_generator_window.hpp" +#include "widgets/spin_button_dim.hpp" +#include +#include "footprint_generator_base.hpp" +#include "footprint_generator_dual.hpp" +#include "footprint_generator_single.hpp" +#include "footprint_generator_quad.hpp" + +namespace horizon { + + + FootprintGeneratorWindow::FootprintGeneratorWindow(BaseObjectType* cobject, const Glib::RefPtr& x) : + Gtk::Window(cobject) { + } + + + void FootprintGeneratorWindow::update_can_generate() { + auto w = dynamic_cast(stack->get_visible_child()); + if(w) + generate_button->set_sensitive(w->property_can_generate()); + else + generate_button->set_sensitive(false); + } + + FootprintGeneratorWindow* FootprintGeneratorWindow::create(Gtk::Window *p, CorePackage *c) { + FootprintGeneratorWindow* w; + Glib::RefPtr x = Gtk::Builder::create(); + x->add_from_resource("/net/carrotIndustries/horizon/imp/footprint_generator/footprint_generator.ui"); + x->get_widget_derived("window", w); + x->get_widget("stack", w->stack); + x->get_widget("generate_button", w->generate_button); + w->set_transient_for(*p); + w->core = c; + + { + auto gen = Gtk::manage(new FootprintGeneratorDual(c)); + gen->show(); + gen->property_can_generate().signal_changed().connect(sigc::mem_fun(w, &FootprintGeneratorWindow::update_can_generate)); + w->stack->add(*gen, "dual", "Dual"); + } + { + auto gen = Gtk::manage(new FootprintGeneratorSingle(c)); + gen->show(); + gen->property_can_generate().signal_changed().connect(sigc::mem_fun(w, &FootprintGeneratorWindow::update_can_generate)); + w->stack->add(*gen, "single", "Single"); + } + { + auto gen = Gtk::manage(new FootprintGeneratorQuad(c)); + gen->show(); + gen->property_can_generate().signal_changed().connect(sigc::mem_fun(w, &FootprintGeneratorWindow::update_can_generate)); + w->stack->add(*gen, "quad", "Quad"); + } + w->stack->property_visible_child().signal_changed().connect(sigc::mem_fun(w, &FootprintGeneratorWindow::update_can_generate)); + w->generate_button->signal_clicked().connect([w]{ + auto gen = dynamic_cast(w->stack->get_visible_child()); + if(gen) { + if(gen->generate()) { + w->core->rebuild(); + w->signal_generated().emit(); + w->hide(); + } + } + }); + return w; + } +} diff --git a/imp/footprint_generator/footprint_generator_window.hpp b/imp/footprint_generator/footprint_generator_window.hpp new file mode 100644 index 000000000..93b0c35ee --- /dev/null +++ b/imp/footprint_generator/footprint_generator_window.hpp @@ -0,0 +1,25 @@ +#pragma once +#include +#include +#include +#include "common.hpp" +#include "uuid.hpp" +#include "core/core_package.hpp" +#include +namespace horizon { + + class FootprintGeneratorWindow: public Gtk::Window{ + public: + FootprintGeneratorWindow(BaseObjectType* cobject, const Glib::RefPtr& x); + static FootprintGeneratorWindow* create(Gtk::Window *p, CorePackage *c); + typedef sigc::signal type_signal_generated; + type_signal_generated signal_generated() {return s_signal_generated;} + + private : + Gtk::Stack *stack; + CorePackage *core; + Gtk::Button *generate_button; + void update_can_generate(); + type_signal_generated s_signal_generated; + }; +} diff --git a/imp/footprint_generator/quad.svg b/imp/footprint_generator/quad.svg new file mode 100644 index 000000000..5ca8f25cb --- /dev/null +++ b/imp/footprint_generator/quad.svg @@ -0,0 +1,856 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/imp/footprint_generator/single.svg b/imp/footprint_generator/single.svg new file mode 100644 index 000000000..62d29da5c --- /dev/null +++ b/imp/footprint_generator/single.svg @@ -0,0 +1,295 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/imp/footprint_generator/svg_overlay.cpp b/imp/footprint_generator/svg_overlay.cpp new file mode 100644 index 000000000..802ba4094 --- /dev/null +++ b/imp/footprint_generator/svg_overlay.cpp @@ -0,0 +1,78 @@ +#include "svg_overlay.hpp" + +namespace horizon { + + SVGOverlay::SVGOverlay(const guint8 *data, gsize data_len) { + init(data, data_len); + } + + SVGOverlay::SVGOverlay(const char *resource) { + auto bytes = Gio::Resource::lookup_data_global(resource); + gsize size {bytes->get_size()}; + init((const guint8*)bytes->get_data(size), size); + } + + bool SVGOverlay::draw(const Cairo::RefPtr &cr) { + rsvg_handle_render_cairo(handle, cr->cobj()); + + + + Pango::FontDescription font = get_style_context()->get_font(); + font.set_weight(Pango::WEIGHT_BOLD); + + auto layout = Pango::Layout::create(cr); + layout->set_font_description(font); + layout->set_alignment(Pango::ALIGN_LEFT); + + RsvgPositionData pos; + RsvgDimensionData dim; + + for(const auto &it: sub_texts) { + rsvg_handle_get_position_sub(handle, &pos, it.first.c_str()); + rsvg_handle_get_dimensions_sub(handle, &dim, it.first.c_str()); + layout->set_text(it.second); + Pango::Rectangle ink, logic; + layout->get_extents(ink, logic); + cr->set_source_rgb(0,0,0); + cr->move_to(pos.x, pos.y+(dim.height-logic.get_height()/PANGO_SCALE)/2); + layout->show_in_cairo_context(cr); + } + return false; + } + + void SVGOverlay::add_at_sub(Gtk::Widget &widget, const char *sub) { + RsvgPositionData pos; + RsvgDimensionData dim; + rsvg_handle_get_position_sub(handle, &pos, sub); + rsvg_handle_get_dimensions_sub(handle, &dim, sub); + auto box = Gtk::manage(new Gtk::Box()); + add_overlay(*box); + box->show(); + box->pack_start(widget, true, true, 0); + box->set_halign(Gtk::ALIGN_START); + box->set_valign(Gtk::ALIGN_START); + box->set_margin_top(pos.y); + box->set_margin_start(pos.x); + box->set_size_request(dim.width, dim.height); + } + + + void SVGOverlay::init(const guint8 *data, gsize data_len) { + handle = rsvg_handle_new_from_data(data, data_len, nullptr); + area = Gtk::manage(new Gtk::DrawingArea()); + RsvgDimensionData dim; + rsvg_handle_get_dimensions(handle, &dim); + area->set_size_request(dim.width, dim.height); + + area->show(); + area->signal_draw().connect(sigc::mem_fun(*this, &SVGOverlay::draw)); + add(*area); + + } + + SVGOverlay::~SVGOverlay() { + g_object_unref(handle); + } + + +} diff --git a/imp/footprint_generator/svg_overlay.hpp b/imp/footprint_generator/svg_overlay.hpp new file mode 100644 index 000000000..fb3425b92 --- /dev/null +++ b/imp/footprint_generator/svg_overlay.hpp @@ -0,0 +1,23 @@ +#pragma once +#include +#include + +namespace horizon { + class SVGOverlay: public Gtk::Overlay { + public: + SVGOverlay(const guint8 *data, gsize data_len); + SVGOverlay(const char *resource); + ~SVGOverlay(); + + void add_at_sub(Gtk::Widget& widget, const char *sub); + std::map sub_texts; + + private: + RsvgHandle *handle=nullptr; + void init(const guint8 *data, gsize data_len); + Gtk::DrawingArea *area; + bool draw(const Cairo::RefPtr &ctx); + + + }; +} diff --git a/imp/imp.cpp b/imp/imp.cpp new file mode 100644 index 000000000..e443cefb6 --- /dev/null +++ b/imp/imp.cpp @@ -0,0 +1,417 @@ +#include "imp.hpp" +#include +#include "block.hpp" +#include "core/core_board.hpp" +#include "core/tool_catalog.hpp" +#include "widgets/spin_button_dim.hpp" +#include "export_gerber/gerber_export.hpp" +#include "part.hpp" +#include + +namespace horizon { + + ImpBase::ImpBase(const std::string &pool_filename): + pool(pool_filename), + core(nullptr) + { + } + + void ImpBase::run(int argc, char *argv[]) { + auto app = Gtk::Application::create(argc, argv, "net.carrotIndustries.horizon.Imp", Gio::APPLICATION_NON_UNIQUE); + + main_window = MainWindow::create(); + canvas = main_window->canvas; + core.r->dialogs.set_parent(main_window); + core.r->dialogs.set_core(core.r); + clipboard.reset(new ClipboardManager(core.r)); + + canvas->signal_selection_changed().connect(sigc::mem_fun(this, &ImpBase::sc)); + canvas->signal_key_press_event().connect(sigc::mem_fun(this, &ImpBase::handle_key_press)); + canvas->signal_cursor_moved().connect(sigc::mem_fun(this, &ImpBase::handle_cursor_move)); + canvas->signal_button_press_event().connect(sigc::mem_fun(this, &ImpBase::handle_click)); + /*main_window->save_button->signal_clicked().connect(sigc::mem_fun(this, &ImpBase::handle_save)); + main_window->print_button->signal_clicked().connect(sigc::mem_fun(this, &ImpBase::handle_print)); + main_window->test_button->signal_clicked().connect(sigc::mem_fun(this, &ImpBase::handle_test));*/ + + panels = Gtk::manage(new PropertyPanels(core.r)); + panels->show_all(); + main_window->property_viewport->add(*panels); + panels->signal_update().connect(sigc::mem_fun(this, &ImpBase::canvas_update_from_pp)); + + warnings_box = Gtk::manage(new WarningsBox()); + warnings_box->signal_selected().connect(sigc::mem_fun(this, &ImpBase::handle_warning_selected)); + main_window->left_panel->pack_end(*warnings_box, false, false, 0); + + tool_popover = Gtk::manage(new ToolPopover(canvas)); + tool_popover->set_position(Gtk::POS_BOTTOM); + tool_popover->signal_tool_activated().connect([this](ToolID tool_id){ + ToolArgs args; + args.coords = canvas->get_cursor_pos(); + args.selection = canvas->get_selection(); + args.work_layer = canvas->property_work_layer(); + ToolResponse r= core.r->tool_begin(tool_id, args); + main_window->active_tool_label->set_text("Active tool: "+core.r->get_tool_name()); + tool_process(r); + }); + + selection_filter_dialog = std::make_unique(this->main_window, &canvas->selection_filter, core.r); + + key_sequence_dialog = std::make_unique(this->main_window); + key_sequence_dialog->add_sequence(std::vector{GDK_KEY_Page_Up}, "Layer up"); + key_sequence_dialog->add_sequence(std::vector{GDK_KEY_Page_Down}, "Layer down"); + key_sequence_dialog->add_sequence(std::vector{GDK_KEY_space}, "Begin tool"); + key_sequence_dialog->add_sequence(std::vector{GDK_KEY_Home}, "Zoom all"); + key_sequence_dialog->add_sequence("Ctrl+Z", "Undo"); + key_sequence_dialog->add_sequence("Ctrl+Y", "Redo"); + key_sequence_dialog->add_sequence("Ctrl+C", "Copy"); + key_sequence_dialog->add_sequence("Ctrl+I", "Selection filter"); + key_sequence_dialog->add_sequence("Esc", "Hover select"); + + + grid_spin_button = Gtk::manage(new SpinButtonDim()); + grid_spin_button->set_range(0.1_mm, 10_mm); + grid_spacing_binding = Glib::Binding::bind_property(grid_spin_button->property_value(), canvas->property_grid_spacing(), Glib::BINDING_BIDIRECTIONAL); + grid_spin_button->set_value(1.25_mm); + grid_spin_button->show_all(); + main_window->top_panel->pack_start(*grid_spin_button, false, false, 0); + + auto save_button = Gtk::manage(new Gtk::Button("Save")); + save_button->signal_clicked().connect([this]{core.r->save();}); + save_button->show(); + main_window->top_panel->pack_start(*save_button, false, false, 0); + + auto selection_filter_button = Gtk::manage(new Gtk::Button("Selection filter")); + selection_filter_button->signal_clicked().connect([this]{selection_filter_dialog->show();}); + selection_filter_button->show(); + main_window->top_panel->pack_start(*selection_filter_button, false, false, 0); + + + core.r->signal_tool_changed().connect([save_button, selection_filter_button](ToolID t){save_button->set_sensitive(t==ToolID::NONE); selection_filter_button->set_sensitive(t==ToolID::NONE);}); + + construct(); + for(const auto &it: key_seq.get_sequences()) { + key_sequence_dialog->add_sequence(it.keys, tool_catalog.at(it.tool_id).name); + } + + canvas_update(); + + auto bbox = core.r->get_bbox(); + canvas->zoom_to_bbox(bbox.first, bbox.second); + + handle_cursor_move(Coordi()); //fixes label + + app->run(*main_window); + } + + void ImpBase::canvas_update_from_pp() { + auto sel = canvas->get_selection(); + canvas_update(); + canvas->set_selection(sel); + } + + void ImpBase::tool_begin(ToolID id) { + ToolArgs args; + args.coords = canvas->get_cursor_pos(); + args.selection = canvas->get_selection(); + args.work_layer = canvas->property_work_layer(); + ToolResponse r= core.r->tool_begin(id, args); + main_window->active_tool_label->set_text("Active tool: "+core.r->get_tool_name()); + tool_process(r); + } + + void ImpBase::add_tool_button(ToolID id, const std::string &label) { + auto button = Gtk::manage(new Gtk::Button(label)); + button->signal_clicked().connect([this, id]{ + tool_begin(id); + }); + button->show(); + core.r->signal_tool_changed().connect([button](ToolID t){button->set_sensitive(t==ToolID::NONE);}); + main_window->top_panel->pack_start(*button, false, false, 0); + } + + bool ImpBase::handle_key_press(GdkEventKey *key_event) { + if(!core.r->tool_is_active()) { + if(key_event->keyval == GDK_KEY_Escape) { + canvas->selection_mode = CanvasGL::SelectionMode::HOVER; + canvas->set_selection({}); + return true; + } + ToolID t = ToolID::NONE; + if(!(key_event->state & Gdk::ModifierType::CONTROL_MASK)) { + t = handle_key(key_event->keyval); + } + + if(t != ToolID::NONE) { + ToolArgs args; + args.coords = canvas->get_cursor_pos(); + args.selection = canvas->get_selection(); + args.work_layer = canvas->property_work_layer(); + ToolResponse r= core.r->tool_begin(t, args); + main_window->active_tool_label->set_text("Active tool: "+core.r->get_tool_name()); + tool_process(r); + return true; + } + else { + if((key_event->keyval == GDK_KEY_Page_Up) || (key_event->keyval == GDK_KEY_Page_Down)) { + int wl = canvas->property_work_layer(); + auto layers = core.r->get_layers_sorted(); + int idx = std::find(layers.begin(), layers.end(), wl) - layers.begin(); + if(key_event->keyval == GDK_KEY_Page_Up) { + idx++; + } + else { + idx--; + } + if(idx>=0 && idx < (int)layers.size()) { + canvas->property_work_layer() = layers.at(idx); + } + return true; + } + else if((key_event->keyval == GDK_KEY_space)) { + Gdk::Rectangle rect; + auto c = canvas->get_cursor_pos_win(); + rect.set_x(c.x); + rect.set_y(c.y); + tool_popover->set_pointing_to(rect); + + std::map can_begin; + auto sel = canvas->get_selection(); + for(const auto &it: tool_catalog) { + bool r = core.r->tool_can_begin(it.first, sel); + can_begin[it.first] = r; + } + tool_popover->set_can_begin(can_begin); + + tool_popover->popup(); + return true; + } + else if((key_event->keyval == GDK_KEY_Home)) { + auto bbox = core.r->get_bbox(); + canvas->zoom_to_bbox(bbox.first, bbox.second); + } + else if(key_event->keyval >= GDK_KEY_0 && key_event->keyval <= GDK_KEY_9) { + int n = key_event->keyval - GDK_KEY_0; + int layer = 0; + if(n == 1) { + layer = 0; + } + else if(n == 2) { + layer = -100; + } + else { + layer = -(n-2); + } + if(core.r->get_layers().count(layer)) { + canvas->property_work_layer() = layer; + } + } + else if(key_event->keyval == GDK_KEY_question) { + key_sequence_dialog->show(); + return true; + } + + if(key_event->state & Gdk::ModifierType::CONTROL_MASK) { + if(key_event->keyval == GDK_KEY_z) { + std::cout << "undo" << std::endl; + core.r->undo(); + canvas_update_from_pp(); + return true; + } + else if(key_event->keyval == GDK_KEY_y) { + std::cout << "redo" << std::endl; + core.r->redo(); + canvas_update_from_pp(); + return true; + } + else if(key_event->keyval == GDK_KEY_c) { + clipboard->copy(canvas->get_selection(), canvas->get_cursor_pos()); + return true; + } + else if(key_event->keyval == GDK_KEY_i) { + selection_filter_dialog->show(); + return true; + } + } + + } + } + else { + ToolArgs args; + args.type = ToolEventType::KEY; + args.coords = canvas->get_cursor_pos(); + args.key = key_event->keyval; + args.work_layer = canvas->property_work_layer(); + ToolResponse r = core.r->tool_update(args); + tool_process(r); + return true; + } + return false; + } + + void ImpBase::handle_cursor_move(const Coordi &pos) { + if(core.r->tool_is_active()) { + ToolArgs args; + args.type = ToolEventType::MOVE; + args.coords = pos; + ToolResponse r = core.r->tool_update(args); + tool_process(r); + } + std::ostringstream ss; + ss << "X:"; + if(pos.x >= 0) { + ss << "+"; + } + else { + ss << "−"; //this is U+2212 MINUS SIGN, has same width as + + } + ss << std::fixed << std::setprecision(3) << std::setw(7) << std::setfill('0') << std::internal << std::abs(pos.x/1e6) << " mm "; + ss << "Y:"; + if(pos.y >= 0) { + ss << "+"; + } + else { + ss << "−"; + } + ss << std::setw(7) << std::abs(pos.y/1e6) << " mm"; + main_window->cursor_label->set_text(ss.str()); + } + + bool ImpBase::handle_click(GdkEventButton *button_event) { + if(core.r->tool_is_active() && button_event->button != 2) { + ToolArgs args; + args.type = ToolEventType::CLICK; + args.coords = canvas->get_cursor_pos(); + args.button = button_event->button; + args.target = canvas->get_current_target(); + args.work_layer = canvas->property_work_layer(); + ToolResponse r = core.r->tool_update(args); + tool_process(r); + } + return false; + } + + void ImpBase::tool_process(const ToolResponse &resp) { + if(!core.r->tool_is_active()) { + main_window->active_tool_label->set_text("Active tool: None"); + main_window->tool_hint_label->set_text(">"); + + } + canvas_update(); + canvas->set_selection(core.r->selection); + if(resp.layer != 10000) { + canvas->property_work_layer() = resp.layer; + } + if(resp.next_tool != ToolID::NONE) { + ToolArgs args; + args.coords = canvas->get_cursor_pos(); + args.keep_selection = true; + ToolResponse r = core.r->tool_begin(resp.next_tool, args); + main_window->active_tool_label->set_text("Active tool: "+core.r->get_tool_name()); + tool_process(r); + } + } + + void ImpBase::sc(void) { + //std::cout << "Selection changed\n"; + //std::cout << "---" << std::endl; + if(!core.r->tool_is_active()) { + auto sel = canvas->get_selection(); + decltype(sel) sel_extra; + for(const auto &it: sel) { + switch(it.type) { + case ObjectType::SCHEMATIC_SYMBOL : + sel_extra.emplace(core.c->get_schematic_symbol(it.uuid)->component->uuid, ObjectType::COMPONENT); + break; + case ObjectType::JUNCTION: + if(core.r->get_junction(it.uuid)->net && core.c) { + sel_extra.emplace(core.r->get_junction(it.uuid)->net->uuid, ObjectType::NET); + } + break; + case ObjectType::LINE_NET: { + LineNet &li = core.c->get_sheet()->net_lines.at(it.uuid); + if(li.net) { + sel_extra.emplace(li.net->uuid, ObjectType::NET); + } + }break; + case ObjectType::NET_LABEL: { + NetLabel &la = core.c->get_sheet()->net_labels.at(it.uuid); + if(la.junction->net) { + sel_extra.emplace(la.junction->net->uuid, ObjectType::NET); + } + }break; + case ObjectType::POWER_SYMBOL: { + PowerSymbol &sym = core.c->get_sheet()->power_symbols.at(it.uuid); + if(sym.net) { + sel_extra.emplace(sym.net->uuid, ObjectType::NET); + } + }break; + case ObjectType::POLYGON_EDGE: + case ObjectType::POLYGON_VERTEX: { + sel_extra.emplace(it.uuid, ObjectType::POLYGON); + }break; + default: ; + } + } + + sel.insert(sel_extra.begin(), sel_extra.end()); + panels->update_objects(sel); + } + //for(const auto it: canvas->get_selection()) { + // std::cout << (std::string)it.uuid << std::endl; + //} + //std::cout << "---" << std::endl; + std::set net_segments; + for(const auto it: canvas->get_selection()) { + if(it.type == ObjectType::LINE_NET) { + auto s_uuid = core.c->get_sheet()->net_lines.at(it.uuid).net_segment; + assert(s_uuid); + net_segments.insert(s_uuid); + } + if(it.type == ObjectType::TRACK) { + auto s_uuid = core.b->get_board()->tracks.at(it.uuid).net_segment; + net_segments.insert(s_uuid); + } + if(it.type == ObjectType::JUNCTION) { + if(core.c) { + auto s_uuid = core.c->get_sheet()->junctions.at(it.uuid).net_segment; + net_segments.insert(s_uuid); + } + if(core.b) { + auto s_uuid = core.b->get_board()->junctions.at(it.uuid).net_segment; + net_segments.insert(s_uuid); + } + } + if(it.type == ObjectType::POWER_SYMBOL) { + if(core.c) { + auto s_uuid = core.c->get_sheet()->power_symbols.at(it.uuid).junction->net_segment; + net_segments.insert(s_uuid); + } + } + } + for(const auto &it :net_segments) { + std::cout << "net seg " <<(std::string)it << std::endl; + } + //std::cout << "---" << std::endl; + } + + void ImpBase::handle_tool_change(ToolID id) { + panels->set_sensitive(id == ToolID::NONE); + canvas->set_selection_allowed(id == ToolID::NONE); + } + + void ImpBase::handle_warning_selected(const Coordi &pos) { + canvas->center_and_zoom(pos); + } + + void ImpBase::key_seq_append_default(KeySequence &ks) { + ks.append_sequence({ + {{GDK_KEY_m}, ToolID::MOVE}, + {{GDK_KEY_M}, ToolID::MOVE_EXACTLY}, + {{GDK_KEY_Delete}, ToolID::DELETE}, + {{GDK_KEY_Return}, ToolID::ENTER_DATUM}, + {{GDK_KEY_r}, ToolID::ROTATE}, + {{GDK_KEY_e}, ToolID::MIRROR}, + {{GDK_KEY_Insert}, ToolID::PASTE}, + }); + } +} diff --git a/imp/imp.hpp b/imp/imp.hpp new file mode 100644 index 000000000..5bd5059c4 --- /dev/null +++ b/imp/imp.hpp @@ -0,0 +1,60 @@ +#pragma once +#include "main_window.hpp" +#include "pool.hpp" +#include "core/core_symbol.hpp" +#include "core/core_schematic.hpp" +#include "core/core_padstack.hpp" +#include "core/core_package.hpp" +#include "property_panels/property_panels.hpp" +#include "widgets/warnings_box.hpp" +#include "widgets/sheet_box.hpp" +#include "widgets/layer_box.hpp" +#include "core/cores.hpp" +#include "core/clipboard.hpp" +#include "key_sequence.hpp" +#include "tool_popover.hpp" +#include "selection_filter_dialog.hpp" +#include "keyseq_dialog.hpp" +#include "widgets/spin_button_dim.hpp" + +namespace horizon { + class ImpBase { + public : + ImpBase(const std::string &pool_path); + void run(int argc, char *argv[]); + virtual void handle_tool_change(ToolID id); + virtual void construct() = 0; + void canvas_update_from_pp(); + + protected : + MainWindow *main_window; + CanvasGL *canvas; + PropertyPanels *panels; + WarningsBox *warnings_box; + ToolPopover *tool_popover; + SpinButtonDim *grid_spin_button; + std::unique_ptr selection_filter_dialog; + + Pool pool; + Cores core; + std::unique_ptr clipboard=nullptr; + std::unique_ptr key_sequence_dialog=nullptr; + Glib::RefPtr grid_spacing_binding; + KeySequence key_seq; + + virtual void canvas_update() = 0; + virtual ToolID handle_key(guint k) = 0; + void sc(void); + bool handle_key_press(GdkEventKey *key_event); + void handle_cursor_move(const Coordi &pos); + bool handle_click(GdkEventButton *button_event); + void tool_process(const ToolResponse &resp); + void tool_begin(ToolID id); + void add_tool_button(ToolID id, const std::string &label); + void handle_warning_selected(const Coordi &pos); + + void key_seq_append_default(KeySequence &ks); + + }; +} + diff --git a/imp/imp_board.cpp b/imp/imp_board.cpp new file mode 100644 index 000000000..731b39ee6 --- /dev/null +++ b/imp/imp_board.cpp @@ -0,0 +1,68 @@ +#include "imp_board.hpp" +#include "json.hpp" +#include "part.hpp" + +namespace horizon { + ImpBoard::ImpBoard(const std::string &board_filename, const std::string &block_filename, const std::string &constraints_filename, const std::string &via_dir, const std::string &pool_path): + ImpLayer(pool_path), + core_board(board_filename, block_filename, constraints_filename, via_dir, pool) { + core = &core_board; + core_board.signal_tool_changed().connect(sigc::mem_fun(this, &ImpBase::handle_tool_change)); + + + key_seq_append_default(key_seq); + key_seq.append_sequence({ + {{GDK_KEY_p, GDK_KEY_j}, ToolID::PLACE_JUNCTION}, + {{GDK_KEY_j}, ToolID::PLACE_JUNCTION}, + {{GDK_KEY_d, GDK_KEY_l}, ToolID::DRAW_LINE}, + {{GDK_KEY_l}, ToolID::DRAW_LINE}, + {{GDK_KEY_d, GDK_KEY_a}, ToolID::DRAW_ARC}, + {{GDK_KEY_a}, ToolID::DRAW_ARC}, + {{GDK_KEY_d, GDK_KEY_y}, ToolID::DRAW_POLYGON}, + {{GDK_KEY_y}, ToolID::DRAW_POLYGON}, + {{GDK_KEY_p, GDK_KEY_t}, ToolID::PLACE_TEXT}, + {{GDK_KEY_t}, ToolID::PLACE_TEXT}, + {{GDK_KEY_p, GDK_KEY_p}, ToolID::MAP_PACKAGE}, + {{GDK_KEY_P}, ToolID::MAP_PACKAGE}, + {{GDK_KEY_d, GDK_KEY_t}, ToolID::DRAW_TRACK}, + {{GDK_KEY_p, GDK_KEY_v}, ToolID::PLACE_VIA}, + {{GDK_KEY_v}, ToolID::PLACE_VIA}, + {{GDK_KEY_x}, ToolID::ROUTE_TRACK}, + {{GDK_KEY_g}, ToolID::DRAG_KEEP_SLOPE}, + }); + key_seq.signal_update_hint().connect([this] (const std::string &s) {main_window->tool_hint_label->set_text(s);}); + + } + + void ImpBoard::canvas_update() { + canvas->update(*core_board.get_canvas_data()); + warnings_box->update(core_board.get_board()->warnings); + } + + + + void ImpBoard::construct() { + canvas->set_core(core.r); + ImpLayer::construct(); + + + auto cam_job_button = Gtk::manage(new Gtk::Button("CAM Job")); + main_window->top_panel->pack_start(*cam_job_button, false, false, 0); + cam_job_button->show(); + cam_job_button->signal_clicked().connect([this]{cam_job_window->show_all();}); + core.r->signal_tool_changed().connect([cam_job_button](ToolID t){cam_job_button->set_sensitive(t==ToolID::NONE);}); + + auto reload_netlist_button = Gtk::manage(new Gtk::Button("Reload netlist")); + main_window->top_panel->pack_start(*reload_netlist_button, false, false, 0); + reload_netlist_button->show(); + reload_netlist_button->signal_clicked().connect([this]{core_board.reload_netlist();canvas_update();}); + core.r->signal_tool_changed().connect([reload_netlist_button](ToolID t){reload_netlist_button->set_sensitive(t==ToolID::NONE);}); + + + cam_job_window = CAMJobWindow::create(main_window, core.b); + } + + ToolID ImpBoard::handle_key(guint k) { + return key_seq.handle_key(k); + } +} diff --git a/imp/imp_board.hpp b/imp/imp_board.hpp new file mode 100644 index 000000000..0547b8caf --- /dev/null +++ b/imp/imp_board.hpp @@ -0,0 +1,25 @@ +#pragma once +#include "imp_layer.hpp" +#include "core/core_board.hpp" +#include "cam_job_dialog.hpp" + +namespace horizon { + class ImpBoard : public ImpLayer { + public : + ImpBoard(const std::string &board_filename, const std::string &block_filename, const std::string &constraints_filename, const std::string &via_dir, const std::string &pool_path); + + const std::map &get_layers(); + + + protected: + void construct() override; + ToolID handle_key(guint k) override; + private: + void canvas_update(); + + CoreBoard core_board; + + CAMJobWindow *cam_job_window; + + }; +} diff --git a/imp/imp_layer.cpp b/imp/imp_layer.cpp new file mode 100644 index 000000000..aad724753 --- /dev/null +++ b/imp/imp_layer.cpp @@ -0,0 +1,31 @@ +#include "imp_layer.hpp" +#include "json.hpp" +#include "part.hpp" + +namespace horizon { + void ImpLayer::construct() { + layer_box = Gtk::manage(new LayerBox(core.r)); + layer_box->show_all(); + main_window->left_panel->pack_start(*layer_box, false, false, 0); + work_layer_binding = Glib::Binding::bind_property(layer_box->property_work_layer(), canvas->property_work_layer(), Glib::BINDING_BIDIRECTIONAL); + layer_opacity_binding = Glib::Binding::bind_property(layer_box->property_layer_opacity(), canvas->property_layer_opacity(), Glib::BINDING_BIDIRECTIONAL); + canvas->property_work_layer().signal_changed().connect([this]{canvas_update_from_pp();}); + layer_box->signal_set_layer_display().connect([this](int index, const LayerDisplay &ld){canvas->set_layer_display(index, ld); canvas_update_from_pp();}); + layer_box->property_select_work_layer_only().signal_changed().connect([this]{canvas->selection_filter.work_layer_only=layer_box->property_select_work_layer_only();}); + core.r->signal_request_save_meta().connect([this] { + json j; + j["layer_display"] = layer_box->serialize(); + j["grid_spacing"] = canvas->property_grid_spacing().get_value(); + return j; + }); + + json j = core.r->get_meta(); + if(!j.is_null()) { + canvas->property_grid_spacing() = j.value("grid_spacing", 1.25_mm); + if(j.count("layer_display")) { + layer_box->load_from_json(j.at("layer_display")); + } + + } + } +} diff --git a/imp/imp_layer.hpp b/imp/imp_layer.hpp new file mode 100644 index 000000000..1b97ec10d --- /dev/null +++ b/imp/imp_layer.hpp @@ -0,0 +1,21 @@ +#pragma once +#include "imp.hpp" + +namespace horizon { + class ImpLayer: public ImpBase { + public: + using ImpBase::ImpBase; + + protected: + void construct() override; + LayerBox *layer_box; + Glib::RefPtr work_layer_binding; + Glib::RefPtr layer_opacity_binding; + + ~ImpLayer() {} + + + }; + + +} diff --git a/imp/imp_main.cpp b/imp/imp_main.cpp new file mode 100644 index 000000000..edaf4b434 --- /dev/null +++ b/imp/imp_main.cpp @@ -0,0 +1,107 @@ +#include "uuid.hpp" +#include "unit.hpp" +#include "symbol.hpp" +#include "json.hpp" +#include "lut.hpp" +#include "common.hpp" +#include "pool.hpp" +#include +#include +#include +#include +#include "canvas/canvas.hpp" +#include "core/core.hpp" +#include "imp.hpp" +#include "imp_symbol.hpp" +#include "imp_schematic.hpp" +#include "imp_padstack.hpp" +#include "imp_package.hpp" +#include "imp_board.hpp" +#include "part.hpp" + +using std::cout; +using horizon::UUID; +using horizon::LutEnumStr; +using horizon::Canvas; + +using json = nlohmann::json; + + +int main(int argc, char *argv[]) { + Glib::OptionContext options; + options.set_summary("horizon interactive manipulator"); + options.set_help_enabled(); + + Glib::OptionGroup group("imp", "imp"); + + bool mode_symbol = false; + Glib::OptionEntry entry; + entry.set_long_name("symbol"); + entry.set_short_name('y'); + entry.set_description("Symbol mode"); + group.add_entry(entry, mode_symbol); + + bool mode_sch = false; + Glib::OptionEntry entry2; + entry2.set_long_name("schematic"); + entry2.set_short_name('c'); + entry2.set_description("Schematic mode"); + group.add_entry(entry2, mode_sch); + + bool mode_padstack = false; + Glib::OptionEntry entry3; + entry3.set_long_name("padstack"); + entry3.set_short_name('a'); + entry3.set_description("Padstack mode"); + group.add_entry(entry3, mode_padstack); + + bool mode_package = false; + Glib::OptionEntry entry4; + entry4.set_long_name("package"); + entry4.set_short_name('k'); + entry4.set_description("Package mode"); + group.add_entry(entry4, mode_package); + + bool mode_board = false; + Glib::OptionEntry entry5; + entry5.set_long_name("board"); + entry5.set_short_name('b'); + entry5.set_description("Board mode"); + group.add_entry(entry5, mode_board); + + std::vector filenames; + Glib::OptionEntry entry_f; + entry_f.set_long_name(G_OPTION_REMAINING); + entry_f.set_short_name(' '); + entry_f.set_description("Filename"); + group.add_entry_filename(entry_f, filenames); + + options.set_main_group(group); + options.parse(argc, argv); + + auto pool_base_path = Glib::getenv("HORIZON_POOL"); + + std::unique_ptr imp = nullptr; + if(mode_sch) { + imp.reset(new horizon::ImpSchematic(filenames.at(0), filenames.at(1), filenames.at(2), pool_base_path)); + } + else if(mode_symbol) { + imp.reset(new horizon::ImpSymbol(filenames.at(0), pool_base_path)); + } + else if(mode_padstack) { + imp.reset(new horizon::ImpPadstack(filenames.at(0), pool_base_path)); + } + else if(mode_package) { + imp.reset(new horizon::ImpPackage(filenames.at(0), pool_base_path)); + } + else if(mode_board) { + imp.reset(new horizon::ImpBoard(filenames.at(0), filenames.at(1), filenames.at(2), filenames.at(3), pool_base_path)); + } + else { + std::cout << "wrong invocation" << std::endl; + return 1; + } + + imp->run(argc, argv); + return 0; +} diff --git a/imp/imp_package.cpp b/imp/imp_package.cpp new file mode 100644 index 000000000..87d69328d --- /dev/null +++ b/imp/imp_package.cpp @@ -0,0 +1,84 @@ +#include "imp_package.hpp" +#include "json.hpp" +#include "part.hpp" +#include "footprint_generator/footprint_generator_window.hpp" + +namespace horizon { + ImpPackage::ImpPackage(const std::string &package_filename, const std::string &pool_path): + ImpLayer(pool_path), + core_package(package_filename, pool) { + core = &core_package; + core_package.signal_tool_changed().connect(sigc::mem_fun(this, &ImpBase::handle_tool_change)); + + + key_seq_append_default(key_seq); + key_seq.append_sequence({ + {{GDK_KEY_p, GDK_KEY_j}, ToolID::PLACE_JUNCTION}, + {{GDK_KEY_j}, ToolID::PLACE_JUNCTION}, + {{GDK_KEY_d, GDK_KEY_l}, ToolID::DRAW_LINE}, + {{GDK_KEY_l}, ToolID::DRAW_LINE}, + {{GDK_KEY_d, GDK_KEY_a}, ToolID::DRAW_ARC}, + {{GDK_KEY_a}, ToolID::DRAW_ARC}, + {{GDK_KEY_d, GDK_KEY_y}, ToolID::DRAW_POLYGON}, + {{GDK_KEY_y}, ToolID::DRAW_POLYGON}, + {{GDK_KEY_p, GDK_KEY_t}, ToolID::PLACE_TEXT}, + {{GDK_KEY_t}, ToolID::PLACE_TEXT}, + {{GDK_KEY_p, GDK_KEY_p}, ToolID::PLACE_PAD}, + {{GDK_KEY_P}, ToolID::PLACE_PAD}, + }); + key_seq.signal_update_hint().connect([this] (const std::string &s) {main_window->tool_hint_label->set_text(s);}); + + } + + void ImpPackage::canvas_update() { + canvas->update(*core_package.get_canvas_data()); + + } + + void ImpPackage::construct() { + canvas->set_core(core.r); + ImpLayer::construct(); + + { + auto button = Gtk::manage(new Gtk::Button("Footprint gen.")); + main_window->top_panel->pack_start(*button, false, false, 0); + button->show(); + button->signal_clicked().connect([this]{footprint_generator_window->show_all();}); + core.r->signal_tool_changed().connect([button](ToolID t){button->set_sensitive(t==ToolID::NONE);}); + } + + auto name_entry = Gtk::manage(new Gtk::Entry()); + name_entry->show(); + main_window->top_panel->pack_start(*name_entry, false, false, 0); + name_entry->set_text(core_package.get_package(false)->name); + core_package.signal_save().connect([this, name_entry]{core_package.get_package(false)->name = name_entry->get_text();}); + + auto tags_entry = Gtk::manage(new Gtk::Entry()); + tags_entry->show(); + main_window->top_panel->pack_start(*tags_entry, false, false, 0); + { + auto pkg = core_package.get_package(false); + std::stringstream s; + std::copy(pkg->tags.begin(), pkg->tags.end(), std::ostream_iterator(s, " ")); + tags_entry->set_text(s.str()); + } + core_package.signal_save().connect([this, tags_entry]{ + auto pkg = core_package.get_package(false); + std::stringstream ss(tags_entry->get_text()); + std::istream_iterator begin(ss); + std::istream_iterator end; + std::vector tags(begin, end); + pkg->tags.clear(); + pkg->tags.insert(tags.begin(), tags.end()); + }); + + footprint_generator_window = FootprintGeneratorWindow::create(main_window, &core_package); + footprint_generator_window->signal_generated().connect(sigc::mem_fun(this, &ImpBase::canvas_update_from_pp)); + + + } + + ToolID ImpPackage::handle_key(guint k) { + return key_seq.handle_key(k); + } +} diff --git a/imp/imp_package.hpp b/imp/imp_package.hpp new file mode 100644 index 000000000..7208c0e8f --- /dev/null +++ b/imp/imp_package.hpp @@ -0,0 +1,19 @@ +#pragma once +#include "imp_layer.hpp" + +namespace horizon { + class ImpPackage : public ImpLayer { + public : + ImpPackage(const std::string &package_filename, const std::string &pool_path); + + + protected: + void construct() override; + ToolID handle_key(guint k) override; + private: + void canvas_update(); + CorePackage core_package; + + class FootprintGeneratorWindow *footprint_generator_window; + }; +} diff --git a/imp/imp_padstack.cpp b/imp/imp_padstack.cpp new file mode 100644 index 000000000..9ed39f1a3 --- /dev/null +++ b/imp/imp_padstack.cpp @@ -0,0 +1,50 @@ +#include "imp_padstack.hpp" +#include "part.hpp" + +namespace horizon { + ImpPadstack::ImpPadstack(const std::string &padstack_filename, const std::string &pool_path): + ImpLayer(pool_path), + core_padstack(padstack_filename, pool) { + core = &core_padstack; + core_padstack.signal_tool_changed().connect(sigc::mem_fun(this, &ImpBase::handle_tool_change)); + key_seq_append_default(key_seq); + key_seq.append_sequence({ + {{GDK_KEY_d, GDK_KEY_y}, ToolID::DRAW_POLYGON}, + {{GDK_KEY_p, GDK_KEY_h}, ToolID::PLACE_HOLE}, + {{GDK_KEY_y}, ToolID::DRAW_POLYGON}, + {{GDK_KEY_h}, ToolID::PLACE_HOLE} + }); + key_seq.signal_update_hint().connect([this] (const std::string &s) {main_window->tool_hint_label->set_text(s);}); + //core_symbol.signal_rebuilt().connect(sigc::mem_fun(this, &ImpBase::handle_core_rebuilt)); + } + + void ImpPadstack::canvas_update() { + canvas->update(*core_padstack.get_canvas_data()); + + } + + void ImpPadstack::construct() { + canvas->set_core(core.r); + ImpLayer::construct(); + + auto name_entry = Gtk::manage(new Gtk::Entry()); + name_entry->show(); + main_window->top_panel->pack_start(*name_entry, false, false, 0); + name_entry->set_text(core_padstack.get_padstack(false)->name); + core_padstack.signal_save().connect([this, name_entry]{core_padstack.get_padstack(false)->name = name_entry->get_text();}); + + auto type_combo = Gtk::manage(new Gtk::ComboBoxText()); + type_combo->append("top", "Top"); + type_combo->append("bottom", "Bottom"); + type_combo->append("through", "Through"); + type_combo->show(); + main_window->top_panel->pack_start(*type_combo, false, false, 0); + type_combo->set_active_id(Padstack::type_lut.lookup_reverse(core_padstack.get_padstack(false)->type)); + core_padstack.signal_save().connect([this, type_combo]{core_padstack.get_padstack(false)->type = Padstack::type_lut.lookup(type_combo->get_active_id());}); + + } + + ToolID ImpPadstack::handle_key(guint k) { + return key_seq.handle_key(k); + } +}; diff --git a/imp/imp_padstack.hpp b/imp/imp_padstack.hpp new file mode 100644 index 000000000..13f3646de --- /dev/null +++ b/imp/imp_padstack.hpp @@ -0,0 +1,18 @@ +#pragma once +#include "imp_layer.hpp" + +namespace horizon { + class ImpPadstack : public ImpLayer { + public : + ImpPadstack(const std::string &symbol_filename, const std::string &pool_path); + + + protected: + void construct() override; + ToolID handle_key(guint k) override; + private: + void canvas_update(); + CorePadstack core_padstack; + + }; +} diff --git a/imp/imp_schematic.cpp b/imp/imp_schematic.cpp new file mode 100644 index 000000000..8a7780aa1 --- /dev/null +++ b/imp/imp_schematic.cpp @@ -0,0 +1,129 @@ +#include "imp_schematic.hpp" +#include "export_pdf.hpp" +#include "part.hpp" + +namespace horizon { + ImpSchematic::ImpSchematic(const std::string &schematic_filename, const std::string &block_filename, const std::string &constraints_filename, const std::string &pool_path) :ImpBase(pool_path), + core_schematic(schematic_filename, block_filename, constraints_filename, pool) + { + core = &core_schematic; + core_schematic.signal_tool_changed().connect(sigc::mem_fun(this, &ImpSchematic::handle_tool_change)); + core_schematic.signal_rebuilt().connect(sigc::mem_fun(this, &ImpSchematic::handle_core_rebuilt)); + + key_seq_append_default(key_seq); + key_seq.append_sequence({ + {{GDK_KEY_p, GDK_KEY_j}, ToolID::PLACE_JUNCTION}, + {{GDK_KEY_j}, ToolID::PLACE_JUNCTION}, + //{{GDK_KEY_d, GDK_KEY_l}, ToolID::DRAW_LINE}, + //{{GDK_KEY_l}, ToolID::DRAW_LINE}, + //{{GDK_KEY_d, GDK_KEY_a}, ToolID::DRAW_ARC}, + //{{GDK_KEY_a}, ToolID::DRAW_ARC}, + {{GDK_KEY_p, GDK_KEY_s}, ToolID::MAP_SYMBOL}, + {{GDK_KEY_s}, ToolID::MAP_SYMBOL}, + {{GDK_KEY_d, GDK_KEY_n}, ToolID::DRAW_NET}, + {{GDK_KEY_n}, ToolID::DRAW_NET}, + {{GDK_KEY_p, GDK_KEY_c}, ToolID::ADD_COMPONENT}, + {{GDK_KEY_c}, ToolID::ADD_COMPONENT}, + {{GDK_KEY_p, GDK_KEY_p}, ToolID::ADD_PART}, + {{GDK_KEY_P}, ToolID::ADD_PART}, + {{GDK_KEY_p, GDK_KEY_t}, ToolID::PLACE_TEXT}, + {{GDK_KEY_t}, ToolID::PLACE_TEXT}, + {{GDK_KEY_p, GDK_KEY_b}, ToolID::PLACE_NET_LABEL}, + {{GDK_KEY_b}, ToolID::PLACE_NET_LABEL}, + {{GDK_KEY_D}, ToolID::DISCONNECT}, + {{GDK_KEY_k}, ToolID::BEND_LINE_NET}, + {{GDK_KEY_g}, ToolID::SELECT_NET_SEGMENT}, + {{GDK_KEY_p, GDK_KEY_o}, ToolID::PLACE_POWER_SYMBOL}, + {{GDK_KEY_o}, ToolID::PLACE_POWER_SYMBOL}, + {{GDK_KEY_v}, ToolID::MOVE_NET_SEGMENT}, + {{GDK_KEY_V}, ToolID::MOVE_NET_SEGMENT_NEW}, + {{GDK_KEY_i}, ToolID::EDIT_COMPONENT_PIN_NAMES}, + {{GDK_KEY_p, GDK_KEY_u}, ToolID::PLACE_BUS_LABEL}, + {{GDK_KEY_u}, ToolID::PLACE_BUS_LABEL}, + {{GDK_KEY_p, GDK_KEY_r}, ToolID::PLACE_BUS_RIPPER}, + {{GDK_KEY_slash}, ToolID::PLACE_BUS_RIPPER}, + {{GDK_KEY_B}, ToolID::MANAGE_BUSES}, + {{GDK_KEY_h}, ToolID::SMASH}, + {{GDK_KEY_H}, ToolID::UNSMASH}, + }); + key_seq.signal_update_hint().connect([this] (const std::string &s) {main_window->tool_hint_label->set_text(s);}); + } + + void ImpSchematic::canvas_update() { + canvas->update(*core_schematic.get_canvas_data()); + warnings_box->update(core_schematic.get_sheet()->warnings); + } + + void ImpSchematic::handle_select_sheet(Sheet *sh) { + if(sh == core_schematic.get_sheet()) + return; + + auto v = canvas->get_scale_and_offset(); + sheet_views[core_schematic.get_sheet()->uuid] = v; + core_schematic.set_sheet(sh->uuid); + canvas_update(); + if(sheet_views.count(sh->uuid)) { + auto v2 = sheet_views.at(sh->uuid); + canvas->set_scale_and_offset(v2.first, v2.second); + } + } + + void ImpSchematic::handle_remove_sheet(Sheet *sh) { + core_schematic.delete_sheet(sh->uuid); + canvas_update(); + } + + void ImpSchematic::construct() { + canvas->set_core(core.r); + sheet_box = Gtk::manage(new SheetBox(&core_schematic)); + sheet_box->show_all(); + sheet_box->signal_add_sheet().connect([this]{core_schematic.add_sheet(); std::cout<<"add sheet"<signal_remove_sheet().connect(sigc::mem_fun(this, &ImpSchematic::handle_remove_sheet)); + sheet_box->signal_select_sheet().connect(sigc::mem_fun(this, &ImpSchematic::handle_select_sheet)); + main_window->left_panel->pack_start(*sheet_box, false, false, 0); + + auto print_button = Gtk::manage(new Gtk::Button("Export PDF")); + print_button->signal_clicked().connect(sigc::mem_fun(this, &ImpSchematic::handle_export_pdf)); + print_button->show(); + main_window->top_panel->pack_start(*print_button, false, false, 0); + + add_tool_button(ToolID::ANNOTATE, "Annotate"); + add_tool_button(ToolID::MANAGE_BUSES, "Buses..."); + add_tool_button(ToolID::ADD_PART, "Place part"); + + core.r->signal_tool_changed().connect([print_button](ToolID t){print_button->set_sensitive(t==ToolID::NONE);}); + + grid_spin_button->set_sensitive(false); + } + + void ImpSchematic::handle_export_pdf() { + Gtk::FileChooserDialog fc(*main_window, "Save PDF", Gtk::FILE_CHOOSER_ACTION_SAVE); + fc.set_do_overwrite_confirmation(true); + if(last_pdf_filename.size()) { + fc.set_filename(last_pdf_filename); + } + else { + fc.set_current_name("schematic.pdf"); + } + fc.add_button("_Cancel", Gtk::RESPONSE_CANCEL); + fc.add_button("_Save", Gtk::RESPONSE_ACCEPT); + if(fc.run()==Gtk::RESPONSE_ACCEPT) { + std::string fn = fc.get_filename(); + last_pdf_filename = fn; + export_pdf(fn, *core.c->get_schematic(), core.r); + } + } + + void ImpSchematic::handle_core_rebuilt() { + sheet_box->update(); + } + + void ImpSchematic::handle_tool_change(ToolID id) { + ImpBase::handle_tool_change(id); + sheet_box->set_sensitive(id == ToolID::NONE); + } + + ToolID ImpSchematic::handle_key(guint k) { + return key_seq.handle_key(k); + } +} diff --git a/imp/imp_schematic.hpp b/imp/imp_schematic.hpp new file mode 100644 index 000000000..9509ec277 --- /dev/null +++ b/imp/imp_schematic.hpp @@ -0,0 +1,28 @@ +#pragma once +#include "imp.hpp" + +namespace horizon { + class ImpSchematic : public ImpBase { + public : + ImpSchematic(const std::string &schematic_filename, const std::string &block_filename, const std::string &constraints_filename, const std::string &pool_path); + + + protected: + void construct() override; + ToolID handle_key(guint k) override; + private: + void canvas_update(); + CoreSchematic core_schematic; + int handle_ask_net_merge(class Net *net, class Net *into); + int handle_ask_delete_component(class Component *comp); + void handle_select_sheet(Sheet *sh); + void handle_remove_sheet(Sheet *sh); + void handle_core_rebuilt(); + void handle_tool_change(ToolID id); + void handle_export_pdf(); + std::string last_pdf_filename; + + std::map> sheet_views; + SheetBox *sheet_box; + }; +} diff --git a/imp/imp_symbol.cpp b/imp/imp_symbol.cpp new file mode 100644 index 000000000..d5510d89b --- /dev/null +++ b/imp/imp_symbol.cpp @@ -0,0 +1,46 @@ +#include "imp_symbol.hpp" +#include "part.hpp" + +namespace horizon { + ImpSymbol::ImpSymbol(const std::string &symbol_filename, const std::string &pool_path): + ImpBase(pool_path), + core_symbol(symbol_filename, pool) { + core = &core_symbol; + core_symbol.signal_tool_changed().connect(sigc::mem_fun(this, &ImpBase::handle_tool_change)); + + key_seq_append_default(key_seq); + key_seq.append_sequence({ + {{GDK_KEY_p, GDK_KEY_j}, ToolID::PLACE_JUNCTION}, + {{GDK_KEY_j}, ToolID::PLACE_JUNCTION}, + {{GDK_KEY_d, GDK_KEY_l}, ToolID::DRAW_LINE}, + {{GDK_KEY_l}, ToolID::DRAW_LINE}, + {{GDK_KEY_d, GDK_KEY_a}, ToolID::DRAW_ARC}, + {{GDK_KEY_a}, ToolID::DRAW_ARC}, + {{GDK_KEY_p, GDK_KEY_p}, ToolID::MAP_PIN}, + {{GDK_KEY_p, GDK_KEY_t}, ToolID::PLACE_TEXT}, + {{GDK_KEY_t}, ToolID::PLACE_TEXT}, + }); + key_seq.signal_update_hint().connect([this] (const std::string &s) {main_window->tool_hint_label->set_text(s);}); + + } + + void ImpSymbol::canvas_update() { + canvas->update(*core_symbol.get_canvas_data()); + } + + void ImpSymbol::construct() { + canvas->set_core(core.r); + + name_entry = Gtk::manage(new Gtk::Entry()); + name_entry->show(); + main_window->top_panel->pack_start(*name_entry, false, false, 0); + name_entry->set_text(core.y->get_symbol()->name); + + core.r->signal_save().connect([this]{core.y->get_symbol(false)->name = name_entry->get_text();}); + grid_spin_button->set_sensitive(false); + } + + ToolID ImpSymbol::handle_key(guint k) { + return key_seq.handle_key(k); + } +} diff --git a/imp/imp_symbol.hpp b/imp/imp_symbol.hpp new file mode 100644 index 000000000..132c12915 --- /dev/null +++ b/imp/imp_symbol.hpp @@ -0,0 +1,19 @@ +#pragma once +#include "imp.hpp" + +namespace horizon { + class ImpSymbol : public ImpBase { + public : + ImpSymbol(const std::string &symbol_filename, const std::string &pool_path); + + + protected: + void construct() override; + ToolID handle_key(guint k) override; + private: + void canvas_update(); + CoreSymbol core_symbol; + + Gtk::Entry *name_entry; + }; +} diff --git a/imp/key_sequence.cpp b/imp/key_sequence.cpp new file mode 100644 index 000000000..3197d7dee --- /dev/null +++ b/imp/key_sequence.cpp @@ -0,0 +1,69 @@ +#include "key_sequence.hpp" +#include +#include +#include + +namespace horizon { + + void KeySequence::append_sequence(const Sequence &s) { + sequences.push_back(s); + } + + void KeySequence::append_sequence(std::initializer_list s) { + for(const auto &it: s) { + append_sequence(it); + } + } + + static std::string hint_from_keys(const std::vector &ks) { + std::string s = ">"; + for(const auto &it: ks) { + char *keyname = gdk_keyval_name(it); + if(keyname) { + s += keyname; + } + } + return s; + } + + const std::vector &KeySequence::get_sequences() { + return sequences; + } + + ToolID KeySequence::handle_key(guint k) { + if(k == GDK_KEY_Escape) { + keys.clear(); + s_signal_update_hint.emit(">"); + return ToolID::NONE; + } + if(k == GDK_KEY_Control_L || k == GDK_KEY_Shift_L) { + return ToolID::NONE; + } + keys.push_back(k); + bool ambigous = false; + for(const auto &it: sequences) { + if(it.keys == keys) { + s_signal_update_hint.emit(hint_from_keys(keys)); + keys.clear(); + return it.tool_id; + } + auto minl = std::min(keys.size(), it.keys.size()); + if(minl == 0) + continue; + if(std::equal(keys.begin(), keys.begin()+minl, it.keys.begin())) { + ambigous = true; + } + } + if(ambigous == false) { + //current sequence is invalid + keys.clear(); + s_signal_update_hint.emit(">Unknown key sequence"); + } + else { + auto s = hint_from_keys(keys)+"?"; + s_signal_update_hint.emit(s); + } + return ToolID::NONE; + } + +} diff --git a/imp/key_sequence.hpp b/imp/key_sequence.hpp new file mode 100644 index 000000000..4b553de27 --- /dev/null +++ b/imp/key_sequence.hpp @@ -0,0 +1,33 @@ +#pragma once +#include +#include "core/core.hpp" +#include + +namespace horizon { + class KeySequence : public sigc::trackable { + public: + class Sequence { + public: + Sequence(std::initializer_list k, ToolID ti): + keys(k), tool_id(ti) {}; + + std::vector keys; + ToolID tool_id; + }; + ToolID handle_key(unsigned int key); + void append_sequence(const Sequence &s); + void append_sequence(std::initializer_list s); + const std::vector &get_sequences(); + + KeySequence() {} + + typedef sigc::signal type_signal_update_hint; + type_signal_update_hint signal_update_hint() {return s_signal_update_hint;} + + private: + std::vector sequences; + std::vector keys; + + type_signal_update_hint s_signal_update_hint; + }; +} diff --git a/imp/keyseq_dialog.cpp b/imp/keyseq_dialog.cpp new file mode 100644 index 000000000..d1c302938 --- /dev/null +++ b/imp/keyseq_dialog.cpp @@ -0,0 +1,58 @@ +#include "keyseq_dialog.hpp" +#include + +namespace horizon { + + static void header_fun(Gtk::ListBoxRow *row, Gtk::ListBoxRow *before) { + if (before && !row->get_header()) { + auto ret = Gtk::manage(new Gtk::Separator); + row->set_header(*ret); + } + } + + KeySequenceDialog::KeySequenceDialog(Gtk::Window *parent) : + Gtk::Dialog("Key Sequences", *parent, Gtk::DialogFlags::DIALOG_MODAL|Gtk::DialogFlags::DIALOG_USE_HEADER_BAR) + { + lb = Gtk::manage(new Gtk::ListBox()); + lb->set_selection_mode(Gtk::SELECTION_NONE); + lb->set_header_func(sigc::ptr_fun(&header_fun)); + + auto sc = Gtk::manage(new Gtk::ScrolledWindow()); + sc->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC); + sc->add(*lb); + + sg = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL); + + get_content_area()->pack_start(*sc, true, true, 0); + get_content_area()->set_border_width(0); + get_content_area()->show_all(); + set_default_size(-1, 500); + } + + void KeySequenceDialog::add_sequence(const std::string &seq, const std::string &label) { + auto box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 20)); + box->set_margin_start(10); + box->set_margin_end(10); + box->set_margin_top(5); + box->set_margin_bottom(5); + auto l1 = Gtk::manage(new Gtk::Label()); + sg->add_widget(*l1); + l1->set_xalign(0); + l1->set_markup(""+seq+""); + auto l2 = Gtk::manage(new Gtk::Label(label)); + l2->set_xalign(0); + box->pack_start(*l1, false, false, 0); + box->pack_start(*l2, true, true, 0); + box->show_all(); + lb->append(*box); + + + } + + void KeySequenceDialog::add_sequence(const std::vector &seq, const std::string &label) { + std::stringstream s; + std::transform(seq.begin(), seq.end(), std::ostream_iterator(s), [](const auto &x){return gdk_keyval_name(x);}); + //std::copy(part.begin(), part.tags.end(), std::ostream_iterator(s, " ")); + add_sequence(s.str(), label); + } +} diff --git a/imp/keyseq_dialog.hpp b/imp/keyseq_dialog.hpp new file mode 100644 index 000000000..a7725dd91 --- /dev/null +++ b/imp/keyseq_dialog.hpp @@ -0,0 +1,17 @@ +#pragma once +#include + +namespace horizon { + class KeySequenceDialog: public Gtk::Dialog { + public: + KeySequenceDialog(Gtk::Window *parent); + void add_sequence(const std::vector &seq, const std::string &label); + void add_sequence(const std::string &seq, const std::string &label); + + private : + Gtk::ListBox *lb; + Glib::RefPtr sg; + }; + + +} diff --git a/imp/main_window.cpp b/imp/main_window.cpp new file mode 100644 index 000000000..1ca26acdd --- /dev/null +++ b/imp/main_window.cpp @@ -0,0 +1,28 @@ +#include "main_window.hpp" +#include + +namespace horizon { + + MainWindow::MainWindow(BaseObjectType* cobject, const Glib::RefPtr& x) : + Gtk::Window(cobject) { + + x->get_widget("gl_container", gl_container); + x->get_widget("active_tool_label", active_tool_label); + x->get_widget("tool_hint_label", tool_hint_label); + x->get_widget("left_panel", left_panel); + x->get_widget("top_panel", top_panel); + x->get_widget("cursor_label", cursor_label); + x->get_widget("property_viewport", property_viewport); + canvas = Gtk::manage(new CanvasGL()); + gl_container->pack_start(*canvas, true, true, 0); + show_all(); + } + + MainWindow* MainWindow::create() { + MainWindow* w; + Glib::RefPtr x = Gtk::Builder::create(); + x->add_from_resource("/net/carrotIndustries/horizon/window.ui"); + x->get_widget_derived("mainWindow", w); + return w; + } +} diff --git a/imp/main_window.hpp b/imp/main_window.hpp new file mode 100644 index 000000000..8af740e83 --- /dev/null +++ b/imp/main_window.hpp @@ -0,0 +1,28 @@ +#pragma once +#include +#include "canvas/canvas.hpp" + +namespace horizon { + + class MainWindow: public Gtk::Window { + public: + MainWindow(BaseObjectType* cobject, const Glib::RefPtr& x); + static MainWindow* create(); + CanvasGL *canvas; + Gtk::Label *active_tool_label; + Gtk::Label *tool_hint_label; + Gtk::Label *cursor_label; + Gtk::Box *left_panel; + Gtk::Box *top_panel; + Gtk::Viewport *property_viewport; + //virtual ~MainWindow(); + private : + + Gtk::Box *gl_container; + + + void sc(void); + void cm(const horizon::Coordi &cursor_pos); + + }; +} diff --git a/imp/selection_filter_dialog.cpp b/imp/selection_filter_dialog.cpp new file mode 100644 index 000000000..a54ee71ae --- /dev/null +++ b/imp/selection_filter_dialog.cpp @@ -0,0 +1,80 @@ +#include "selection_filter_dialog.hpp" + +namespace horizon { + + static void header_fun(Gtk::ListBoxRow *row, Gtk::ListBoxRow *before) { + if (before && !row->get_header()) { + auto ret = Gtk::manage(new Gtk::Separator); + row->set_header(*ret); + } + } + + SelectionFilterDialog::SelectionFilterDialog(Gtk::Window *parent, SelectionFilter *sf, Core *c) : + Gtk::Window(), + selection_filter(sf), + core(c){ + set_default_size(200, 300); + set_type_hint(Gdk::WINDOW_TYPE_HINT_DIALOG); + set_transient_for(*parent); + auto hb = Gtk::manage(new Gtk::HeaderBar()); + hb->set_show_close_button(true); + set_titlebar(*hb); + hb->show(); + set_title("Selection filter"); + + auto reset_button = Gtk::manage(new Gtk::Button()); + reset_button->set_image_from_icon_name("edit-select-all-symbolic", Gtk::ICON_SIZE_BUTTON); + reset_button->show_all(); + reset_button->signal_clicked().connect([this]{ + for(auto cb: checkbuttons) { + cb->set_active(true); + } + }); + hb->pack_start(*reset_button); + reset_button->show(); + + /*auto cssp = Gtk::CssProvider::create(); + cssp->load_from_data(".imp-tiny-button {min-width:0px; min-height:0px; padding:0px;}"); + Gtk::StyleContext::add_provider_for_screen(Gdk::Screen::get_default(), cssp, 700);*/ + + listbox = Gtk::manage(new Gtk::ListBox()); + listbox->set_selection_mode(Gtk::SELECTION_NONE); + listbox->set_header_func(sigc::ptr_fun(&header_fun)); + for(const auto &it: object_descriptions) { + auto ot = it.first; + if(ot == ObjectType::POLYGON) + continue; + if(core->has_object_type(ot)) { + auto cb = Gtk::manage(new Gtk::CheckButton(it.second.name_pl)); + cb->set_active(true); + cb->signal_toggled().connect([this, ot, cb] {selection_filter->object_filter[ot]=cb->get_active();}); + checkbuttons.push_back(cb); + auto bbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 2)); + + + auto only_button = Gtk::manage(new Gtk::Button()); + only_button->set_margin_start(5); + //only_button->set_margin_top(2); + //only_button->set_margin_bottom(1); + only_button->set_image_from_icon_name("pan-end-symbolic", Gtk::ICON_SIZE_BUTTON); + only_button->set_relief(Gtk::RELIEF_NONE); + only_button->signal_clicked().connect([this, ot, cb] { + for(auto cb_other: checkbuttons) { + cb_other->set_active(cb_other==cb); + } + }); + + bbox->pack_start(*only_button, false, false, 0); + bbox->pack_start(*cb, true, true, 0); + + listbox->append(*bbox); + } + } + + + auto sc = Gtk::manage(new Gtk::ScrolledWindow()); + sc->add(*listbox); + add(*sc); + sc->show_all(); + } +} diff --git a/imp/selection_filter_dialog.hpp b/imp/selection_filter_dialog.hpp new file mode 100644 index 000000000..b3fbbaf5b --- /dev/null +++ b/imp/selection_filter_dialog.hpp @@ -0,0 +1,22 @@ +#pragma once +#include +#include +#include +#include "common.hpp" +#include "uuid.hpp" +#include "canvas/selection_filter.hpp" +#include "core/core.hpp" +namespace horizon { + + + class SelectionFilterDialog: public Gtk::Window { + public: + SelectionFilterDialog(Gtk::Window *parent, SelectionFilter *sf, Core *c); + private : + + SelectionFilter *selection_filter; + Core *core; + Gtk::ListBox *listbox = nullptr; + std::vector checkbuttons; + }; +} diff --git a/imp/tool_popover.cpp b/imp/tool_popover.cpp new file mode 100644 index 000000000..790bbf0dc --- /dev/null +++ b/imp/tool_popover.cpp @@ -0,0 +1,97 @@ +#include "tool_popover.hpp" +#include "core/tool_catalog.hpp" + +namespace horizon { + ToolPopover::ToolPopover(Gtk::Widget *parent):Gtk::Popover(*parent) { + auto box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL, 4)); + search_entry = Gtk::manage(new Gtk::SearchEntry()); + box->pack_start(*search_entry, false, false, 0); + + + + store = Gtk::ListStore::create(list_columns); + store->set_sort_column(list_columns.name, Gtk::SORT_ASCENDING); + + store_filtered = Gtk::TreeModelFilter::create(store); + store_filtered->set_visible_func([this](const Gtk::TreeModel::const_iterator& it)->bool{ + const std::string search = search_entry->get_text(); + const Gtk::TreeModel::Row row = *it; + if(row[list_columns.can_begin] == false) + return false; + //std::string name = it; + Glib::ustring tool_name = row[list_columns.name]; + auto r = std::search(tool_name.begin(), tool_name.end(), search.begin(), search.end(), [](const auto &a, const auto &b)->bool{ + return std::tolower(a) == std::tolower(b); + }); + return r != tool_name.end(); + + }); + search_entry->signal_search_changed().connect([this]{ + store_filtered->refilter(); + if(store_filtered->children().size()) + view->get_selection()->select(store_filtered->children().begin()); + auto it = view->get_selection()->get_selected(); + if(it) { + view->scroll_to_row(store_filtered->get_path(it)); + } + }); + + + view = Gtk::manage(new Gtk::TreeView(store_filtered)); + view->get_selection()->set_mode(Gtk::SELECTION_BROWSE); + view->append_column("Tool", list_columns.name); + view->set_enable_search(false); + view->signal_key_press_event().connect([this](GdkEventKey* ev)->bool{std::cout << "handle ev" << std::endl; search_entry->grab_focus_without_selecting(); return search_entry->handle_event(ev);}); + + search_entry->signal_activate().connect(sigc::mem_fun(this, &ToolPopover::emit_tool_activated)); + ///search_entry->signal_activate().connect([this]{std::cout << "entry activate" << std::endl;}); + view->signal_row_activated().connect([this](auto a, auto b) {this->emit_tool_activated();}); + + auto sc = Gtk::manage(new Gtk::ScrolledWindow()); + sc->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC); + sc->set_min_content_height(200); + sc->add(*view); + + box->pack_start(*sc, true, true, 0); + + Gtk::TreeModel::Row row; + for(const auto &it: tool_catalog) { + row = *(store->append()); + row[list_columns.name] = it.second.name; + row[list_columns.tool_id] = it.first; + row[list_columns.can_begin] = true; + } + + + + add(*box); + box->show_all(); + } + + void ToolPopover::emit_tool_activated() { + auto it = view->get_selection()->get_selected(); + if(it) { + popdown(); + Gtk::TreeModel::Row row = *it; + s_signal_tool_activated.emit(row[list_columns.tool_id]); + + } + } + + void ToolPopover::set_can_begin(const std::map &can_begin) { + for(auto &it: store->children()) { + if(can_begin.count(it[list_columns.tool_id])) { + it[list_columns.can_begin] = can_begin.at(it[list_columns.tool_id]); + } + else { + it[list_columns.can_begin] = true; + } + } + } + + void ToolPopover::on_show() { + Gtk::Popover::on_show(); + search_entry->select_region(0, -1); + } + +} diff --git a/imp/tool_popover.hpp b/imp/tool_popover.hpp new file mode 100644 index 000000000..6e30f9976 --- /dev/null +++ b/imp/tool_popover.hpp @@ -0,0 +1,37 @@ +#pragma once +#include +#include "core/core.hpp" + +namespace horizon { + + class ToolPopover : public Gtk::Popover { + public: + ToolPopover(Gtk::Widget *parent); + typedef sigc::signal type_signal_tool_activated; + //type_signal_selected signal_selected() {return s_signal_selected;} + type_signal_tool_activated signal_tool_activated() {return s_signal_tool_activated;} + void set_can_begin(const std::map &can_begin); + + private: + Gtk::SearchEntry *search_entry; + class ListColumns : public Gtk::TreeModelColumnRecord { + public: + ListColumns() { + Gtk::TreeModelColumnRecord::add( name ) ; + Gtk::TreeModelColumnRecord::add( tool_id ) ; + Gtk::TreeModelColumnRecord::add( can_begin ) ; + } + Gtk::TreeModelColumn name; + Gtk::TreeModelColumn tool_id; + Gtk::TreeModelColumn can_begin; + } ; + ListColumns list_columns; + Gtk::TreeView *view; + Glib::RefPtr store; + Glib::RefPtr store_filtered; + void emit_tool_activated(); + type_signal_tool_activated s_signal_tool_activated; + void on_show() override; + + }; +} diff --git a/json.hpp b/json.hpp new file mode 100644 index 000000000..83cbfdb1a --- /dev/null +++ b/json.hpp @@ -0,0 +1,10626 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ +| | |__ | | | | | | version 2.0.6 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +Copyright (c) 2013-2016 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef NLOHMANN_JSON_HPP +#define NLOHMANN_JSON_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// exclude unsupported compilers +#if defined(__clang__) + #define CLANG_VERSION (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) + #if CLANG_VERSION < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif +#elif defined(__GNUC__) + #define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) + #if GCC_VERSION < 40900 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif +#endif + +// disable float-equal warnings on GCC/clang +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +// allow for portable deprecation warnings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define JSON_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) + #define JSON_DEPRECATED __declspec(deprecated) +#else + #define JSON_DEPRECATED +#endif + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ + + +/*! +@brief unnamed namespace with internal helper functions +@since version 1.0.0 +*/ +namespace +{ +/*! +@brief Helper to determine whether there's a key_type for T. + +Thus helper is used to tell associative containers apart from other containers +such as sequence containers. For instance, `std::map` passes the test as it +contains a `mapped_type`, whereas `std::vector` fails the test. + +@sa http://stackoverflow.com/a/7728728/266378 +@since version 1.0.0, overworked in version 2.0.6 +*/ +template +struct has_mapped_type +{ + private: + template + static int detect(U&&); + + static void detect(...); + public: + static constexpr bool value = + std::is_integral()))>::value; +}; + +/*! +@brief helper class to create locales with decimal point + +This struct is used a default locale during the JSON serialization. JSON +requires the decimal point to be `.`, so this function overloads the +`do_decimal_point()` function to return `.`. This function is called by +float-to-string conversions to retrieve the decimal separator between integer +and fractional parts. + +@sa https://github.com/nlohmann/json/issues/51#issuecomment-86869315 +@since version 2.0.0 +*/ +struct DecimalSeparator : std::numpunct +{ + char do_decimal_point() const + { + return '.'; + } +}; + +} + +/*! +@brief a class to store JSON values + +@tparam ObjectType type for JSON objects (`std::map` by default; will be used +in @ref object_t) +@tparam ArrayType type for JSON arrays (`std::vector` by default; will be used +in @ref array_t) +@tparam StringType type for JSON strings and object keys (`std::string` by +default; will be used in @ref string_t) +@tparam BooleanType type for JSON booleans (`bool` by default; will be used +in @ref boolean_t) +@tparam NumberIntegerType type for JSON integer numbers (`int64_t` by +default; will be used in @ref number_integer_t) +@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c +`uint64_t` by default; will be used in @ref number_unsigned_t) +@tparam NumberFloatType type for JSON floating-point numbers (`double` by +default; will be used in @ref number_float_t) +@tparam AllocatorType type of the allocator to use (`std::allocator` by +default) + +@requirement The class satisfies the following concept requirements: +- Basic + - [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible): + JSON values can be default constructed. The result will be a JSON null value. + - [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible): + A JSON value can be constructed from an rvalue argument. + - [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible): + A JSON value can be copy-constructed from an lvalue expression. + - [MoveAssignable](http://en.cppreference.com/w/cpp/concept/MoveAssignable): + A JSON value van be assigned from an rvalue argument. + - [CopyAssignable](http://en.cppreference.com/w/cpp/concept/CopyAssignable): + A JSON value can be copy-assigned from an lvalue expression. + - [Destructible](http://en.cppreference.com/w/cpp/concept/Destructible): + JSON values can be destructed. +- Layout + - [StandardLayoutType](http://en.cppreference.com/w/cpp/concept/StandardLayoutType): + JSON values have + [standard layout](http://en.cppreference.com/w/cpp/language/data_members#Standard_layout): + All non-static data members are private and standard layout types, the class + has no virtual functions or (virtual) base classes. +- Library-wide + - [EqualityComparable](http://en.cppreference.com/w/cpp/concept/EqualityComparable): + JSON values can be compared with `==`, see @ref + operator==(const_reference,const_reference). + - [LessThanComparable](http://en.cppreference.com/w/cpp/concept/LessThanComparable): + JSON values can be compared with `<`, see @ref + operator<(const_reference,const_reference). + - [Swappable](http://en.cppreference.com/w/cpp/concept/Swappable): + Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of + other compatible types, using unqualified function call @ref swap(). + - [NullablePointer](http://en.cppreference.com/w/cpp/concept/NullablePointer): + JSON values can be compared against `std::nullptr_t` objects which are used + to model the `null` value. +- Container + - [Container](http://en.cppreference.com/w/cpp/concept/Container): + JSON values can be used like STL containers and provide iterator access. + - [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer); + JSON values can be used like STL containers and provide reverse iterator + access. + +@invariant The member variables @a m_value and @a m_type have the following +relationship: +- If `m_type == value_t::object`, then `m_value.object != nullptr`. +- If `m_type == value_t::array`, then `m_value.array != nullptr`. +- If `m_type == value_t::string`, then `m_value.string != nullptr`. +The invariants are checked by member function assert_invariant(). + +@internal +@note ObjectType trick from http://stackoverflow.com/a/9860911 +@endinternal + +@see [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange +Format](http://rfc7159.net/rfc7159) + +@since version 1.0.0 + +@nosubgrouping +*/ +template < + template class ObjectType = std::map, + template class ArrayType = std::vector, + class StringType = std::string, + class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator + > +class basic_json +{ + private: + /// workaround type for MSVC + using basic_json_t = basic_json; + + public: + // forward declarations + template class json_reverse_iterator; + class json_pointer; + + ///////////////////// + // container types // + ///////////////////// + + /// @name container types + /// The canonic container types to use @ref basic_json like any other STL + /// container. + /// @{ + + /// the type of elements in a basic_json container + using value_type = basic_json; + + /// the type of an element reference + using reference = value_type&; + /// the type of an element const reference + using const_reference = const value_type&; + + /// a type to represent differences between iterators + using difference_type = std::ptrdiff_t; + /// a type to represent container sizes + using size_type = std::size_t; + + /// the allocator type + using allocator_type = AllocatorType; + + /// the type of an element pointer + using pointer = typename std::allocator_traits::pointer; + /// the type of an element const pointer + using const_pointer = typename std::allocator_traits::const_pointer; + + /// an iterator for a basic_json container + class iterator; + /// a const iterator for a basic_json container + class const_iterator; + /// a reverse iterator for a basic_json container + using reverse_iterator = json_reverse_iterator; + /// a const reverse iterator for a basic_json container + using const_reverse_iterator = json_reverse_iterator; + + /// @} + + + /*! + @brief returns the allocator associated with the container + */ + static allocator_type get_allocator() + { + return allocator_type(); + } + + + /////////////////////////// + // JSON value data types // + /////////////////////////// + + /// @name JSON value data types + /// The data types to store a JSON value. These types are derived from + /// the template arguments passed to class @ref basic_json. + /// @{ + + /*! + @brief a type for an object + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON objects as follows: + > An object is an unordered collection of zero or more name/value pairs, + > where a name is a string and a value is a string, number, boolean, null, + > object, or array. + + To store objects in C++, a type is defined by the template parameters + described below. + + @tparam ObjectType the container to store objects (e.g., `std::map` or + `std::unordered_map`) + @tparam StringType the type of the keys or names (e.g., `std::string`). + The comparison function `std::less` is used to order elements + inside the container. + @tparam AllocatorType the allocator to use for objects (e.g., + `std::allocator`) + + #### Default type + + With the default values for @a ObjectType (`std::map`), @a StringType + (`std::string`), and @a AllocatorType (`std::allocator`), the default + value for @a object_t is: + + @code {.cpp} + std::map< + std::string, // key_type + basic_json, // value_type + std::less, // key_compare + std::allocator> // allocator_type + > + @endcode + + #### Behavior + + The choice of @a object_t influences the behavior of the JSON class. With + the default type, objects have the following behavior: + + - When all names are unique, objects will be interoperable in the sense + that all software implementations receiving that object will agree on + the name-value mappings. + - When the names within an object are not unique, later stored name/value + pairs overwrite previously stored name/value pairs, leaving the used + names unique. For instance, `{"key": 1}` and `{"key": 2, "key": 1}` will + be treated as equal and both stored as `{"key": 1}`. + - Internally, name/value pairs are stored in lexicographical order of the + names. Objects will also be serialized (see @ref dump) in this order. + For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored + and serialized as `{"a": 2, "b": 1}`. + - When comparing objects, the order of the name/value pairs is irrelevant. + This makes objects interoperable in the sense that they will not be + affected by these differences. For instance, `{"b": 1, "a": 2}` and + `{"a": 2, "b": 1}` will be treated as equal. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the object's limit of nesting is not constraint explicitly. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON object. + + #### Storage + + Objects are stored as pointers in a @ref basic_json type. That is, for any + access to object values, a pointer of type `object_t*` must be + dereferenced. + + @sa @ref array_t -- type for an array value + + @since version 1.0.0 + + @note The order name/value pairs are added to the object is *not* + preserved by the library. Therefore, iterating an object may return + name/value pairs in a different order than they were originally stored. In + fact, keys will be traversed in alphabetical order as `std::map` with + `std::less` is used by default. Please note this behavior conforms to [RFC + 7159](http://rfc7159.net/rfc7159), because any order implements the + specified "unordered" nature of JSON objects. + */ + using object_t = ObjectType, + AllocatorType>>; + + /*! + @brief a type for an array + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows: + > An array is an ordered sequence of zero or more values. + + To store objects in C++, a type is defined by the template parameters + explained below. + + @tparam ArrayType container type to store arrays (e.g., `std::vector` or + `std::list`) + @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`) + + #### Default type + + With the default values for @a ArrayType (`std::vector`) and @a + AllocatorType (`std::allocator`), the default value for @a array_t is: + + @code {.cpp} + std::vector< + basic_json, // value_type + std::allocator // allocator_type + > + @endcode + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the array's limit of nesting is not constraint explicitly. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON array. + + #### Storage + + Arrays are stored as pointers in a @ref basic_json type. That is, for any + access to array values, a pointer of type `array_t*` must be dereferenced. + + @sa @ref object_t -- type for an object value + + @since version 1.0.0 + */ + using array_t = ArrayType>; + + /*! + @brief a type for a string + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows: + > A string is a sequence of zero or more Unicode characters. + + To store objects in C++, a type is defined by the template parameter + described below. Unicode values are split by the JSON class into + byte-sized characters during deserialization. + + @tparam StringType the container to store strings (e.g., `std::string`). + Note this container is used for keys/names in objects, see @ref object_t. + + #### Default type + + With the default values for @a StringType (`std::string`), the default + value for @a string_t is: + + @code {.cpp} + std::string + @endcode + + #### String comparison + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > Software implementations are typically required to test names of object + > members for equality. Implementations that transform the textual + > representation into sequences of Unicode code units and then perform the + > comparison numerically, code unit by code unit, are interoperable in the + > sense that implementations will agree in all cases on equality or + > inequality of two strings. For example, implementations that compare + > strings with escaped characters unconverted may incorrectly find that + > `"a\\b"` and `"a\u005Cb"` are not equal. + + This implementation is interoperable as it does compare strings code unit + by code unit. + + #### Storage + + String values are stored as pointers in a @ref basic_json type. That is, + for any access to string values, a pointer of type `string_t*` must be + dereferenced. + + @since version 1.0.0 + */ + using string_t = StringType; + + /*! + @brief a type for a boolean + + [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a + type which differentiates the two literals `true` and `false`. + + To store objects in C++, a type is defined by the template parameter @a + BooleanType which chooses the type to use. + + #### Default type + + With the default values for @a BooleanType (`bool`), the default value for + @a boolean_t is: + + @code {.cpp} + bool + @endcode + + #### Storage + + Boolean values are stored directly inside a @ref basic_json type. + + @since version 1.0.0 + */ + using boolean_t = BooleanType; + + /*! + @brief a type for a number (integer) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store integer numbers in C++, a type is defined by the template + parameter @a NumberIntegerType which chooses the type to use. + + #### Default type + + With the default values for @a NumberIntegerType (`int64_t`), the default + value for @a number_integer_t is: + + @code {.cpp} + int64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `9223372036854775807` (INT64_MAX) and the minimal integer number + that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers + that are out of range will yield over/underflow when used in a + constructor. During deserialization, too large or small integer numbers + will be automatically be stored as @ref number_unsigned_t or @ref + number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange of the exactly supported range [INT64_MIN, + INT64_MAX], this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_integer_t = NumberIntegerType; + + /*! + @brief a type for a number (unsigned) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store unsigned integer numbers in C++, a type is defined by the + template parameter @a NumberUnsignedType which chooses the type to use. + + #### Default type + + With the default values for @a NumberUnsignedType (`uint64_t`), the + default value for @a number_unsigned_t is: + + @code {.cpp} + uint64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `18446744073709551615` (UINT64_MAX) and the minimal integer + number that can be stored is `0`. Integer numbers that are out of range + will yield over/underflow when used in a constructor. During + deserialization, too large or small integer numbers will be automatically + be stored as @ref number_integer_t or @ref number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange (when considered in conjunction with the + number_integer_t type) of the exactly supported range [0, UINT64_MAX], + this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + @sa @ref number_integer_t -- type for number values (integer) + + @since version 2.0.0 + */ + using number_unsigned_t = NumberUnsignedType; + + /*! + @brief a type for a number (floating-point) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store floating-point numbers in C++, a type is defined by the template + parameter @a NumberFloatType which chooses the type to use. + + #### Default type + + With the default values for @a NumberFloatType (`double`), the default + value for @a number_float_t is: + + @code {.cpp} + double + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in floating-point literals will be ignored. Internally, + the value will be stored as decimal number. For instance, the C++ + floating-point literal `01.2` will be serialized to `1.2`. During + deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > This specification allows implementations to set limits on the range and + > precision of numbers accepted. Since software that implements IEEE + > 754-2008 binary64 (double precision) numbers is generally available and + > widely used, good interoperability can be achieved by implementations + > that expect no more precision or range than these provide, in the sense + > that implementations will approximate JSON numbers within the expected + > precision. + + This implementation does exactly follow this approach, as it uses double + precision floating-point numbers. Note values smaller than + `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` + will be stored as NaN internally and be serialized to `null`. + + #### Storage + + Floating-point number values are stored directly inside a @ref basic_json + type. + + @sa @ref number_integer_t -- type for number values (integer) + + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_float_t = NumberFloatType; + + /// @} + + + /////////////////////////// + // JSON type enumeration // + /////////////////////////// + + /*! + @brief the JSON type enumeration + + This enumeration collects the different JSON types. It is internally used + to distinguish the stored values, and the functions @ref is_null(), @ref + is_object(), @ref is_array(), @ref is_string(), @ref is_boolean(), @ref + is_number() (with @ref is_number_integer(), @ref is_number_unsigned(), and + @ref is_number_float()), @ref is_discarded(), @ref is_primitive(), and + @ref is_structured() rely on it. + + @note There are three enumeration entries (number_integer, + number_unsigned, and number_float), because the library distinguishes + these three types for numbers: @ref number_unsigned_t is used for unsigned + integers, @ref number_integer_t is used for signed integers, and @ref + number_float_t is used for floating-point numbers or to approximate + integers which do not fit in the limits of their respective type. + + @sa @ref basic_json(const value_t value_type) -- create a JSON value with + the default value for a given type + + @since version 1.0.0 + */ + enum class value_t : uint8_t + { + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + discarded ///< discarded by the the parser callback function + }; + + + private: + + /// helper for exception-safe object creation + template + static T* create(Args&& ... args) + { + AllocatorType alloc; + auto deleter = [&](T * object) + { + alloc.deallocate(object, 1); + }; + std::unique_ptr object(alloc.allocate(1), deleter); + alloc.construct(object.get(), std::forward(args)...); + assert(object.get() != nullptr); + return object.release(); + } + + //////////////////////// + // JSON value storage // + //////////////////////// + + /*! + @brief a JSON value + + The actual storage for a JSON value of the @ref basic_json class. This + union combines the different storage types for the JSON value types + defined in @ref value_t. + + JSON type | value_t type | used type + --------- | --------------- | ------------------------ + object | object | pointer to @ref object_t + array | array | pointer to @ref array_t + string | string | pointer to @ref string_t + boolean | boolean | @ref boolean_t + number | number_integer | @ref number_integer_t + number | number_unsigned | @ref number_unsigned_t + number | number_float | @ref number_float_t + null | null | *no value is stored* + + @note Variable-length types (objects, arrays, and strings) are stored as + pointers. The size of the union should not exceed 64 bits if the default + value types are used. + + @since version 1.0.0 + */ + union json_value + { + /// object (stored with pointer to save storage) + object_t* object; + /// array (stored with pointer to save storage) + array_t* array; + /// string (stored with pointer to save storage) + string_t* string; + /// boolean + boolean_t boolean; + /// number (integer) + number_integer_t number_integer; + /// number (unsigned integer) + number_unsigned_t number_unsigned; + /// number (floating-point) + number_float_t number_float; + + /// default constructor (for null values) + json_value() = default; + /// constructor for booleans + json_value(boolean_t v) noexcept : boolean(v) {} + /// constructor for numbers (integer) + json_value(number_integer_t v) noexcept : number_integer(v) {} + /// constructor for numbers (unsigned) + json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} + /// constructor for numbers (floating-point) + json_value(number_float_t v) noexcept : number_float(v) {} + /// constructor for empty values of a given type + json_value(value_t t) + { + switch (t) + { + case value_t::object: + { + object = create(); + break; + } + + case value_t::array: + { + array = create(); + break; + } + + case value_t::string: + { + string = create(""); + break; + } + + case value_t::boolean: + { + boolean = boolean_t(false); + break; + } + + case value_t::number_integer: + { + number_integer = number_integer_t(0); + break; + } + + case value_t::number_unsigned: + { + number_unsigned = number_unsigned_t(0); + break; + } + + case value_t::number_float: + { + number_float = number_float_t(0.0); + break; + } + + default: + { + break; + } + } + } + + /// constructor for strings + json_value(const string_t& value) + { + string = create(value); + } + + /// constructor for objects + json_value(const object_t& value) + { + object = create(value); + } + + /// constructor for arrays + json_value(const array_t& value) + { + array = create(value); + } + }; + + /*! + @brief checks the class invariants + + This function asserts the class invariants. It needs to be called at the + end of every constructor to make sure that created objects respect the + invariant. Furthermore, it has to be called each time the type of a JSON + value is changed, because the invariant expresses a relationship between + @a m_type and @a m_value. + */ + void assert_invariant() const + { + assert(m_type != value_t::object or m_value.object != nullptr); + assert(m_type != value_t::array or m_value.array != nullptr); + assert(m_type != value_t::string or m_value.string != nullptr); + } + + public: + ////////////////////////// + // JSON parser callback // + ////////////////////////// + + /*! + @brief JSON callback events + + This enumeration lists the parser events that can trigger calling a + callback function of type @ref parser_callback_t during parsing. + + @image html callback_events.png "Example when certain parse events are triggered" + + @since version 1.0.0 + */ + enum class parse_event_t : uint8_t + { + /// the parser read `{` and started to process a JSON object + object_start, + /// the parser read `}` and finished processing a JSON object + object_end, + /// the parser read `[` and started to process a JSON array + array_start, + /// the parser read `]` and finished processing a JSON array + array_end, + /// the parser read a key of a value in an object + key, + /// the parser finished reading a JSON value + value + }; + + /*! + @brief per-element parser callback type + + With a parser callback function, the result of parsing a JSON text can be + influenced. When passed to @ref parse(std::istream&, const + parser_callback_t) or @ref parse(const char*, const parser_callback_t), + it is called on certain events (passed as @ref parse_event_t via parameter + @a event) with a set recursion depth @a depth and context JSON value + @a parsed. The return value of the callback function is a boolean + indicating whether the element that emitted the callback shall be kept or + not. + + We distinguish six scenarios (determined by the event type) in which the + callback function can be called. The following table describes the values + of the parameters @a depth, @a event, and @a parsed. + + parameter @a event | description | parameter @a depth | parameter @a parsed + ------------------ | ----------- | ------------------ | ------------------- + parse_event_t::object_start | the parser read `{` and started to process a JSON object | depth of the parent of the JSON object | a JSON value with type discarded + parse_event_t::key | the parser read a key of a value in an object | depth of the currently parsed JSON object | a JSON string containing the key + parse_event_t::object_end | the parser read `}` and finished processing a JSON object | depth of the parent of the JSON object | the parsed JSON object + parse_event_t::array_start | the parser read `[` and started to process a JSON array | depth of the parent of the JSON array | a JSON value with type discarded + parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array + parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value + + @image html callback_events.png "Example when certain parse events are triggered" + + Discarding a value (i.e., returning `false`) has different effects + depending on the context in which function was called: + + - Discarded values in structured types are skipped. That is, the parser + will behave as if the discarded value was never read. + - In case a value outside a structured type is skipped, it is replaced + with `null`. This case happens if the top-level element is skipped. + + @param[in] depth the depth of the recursion during parsing + + @param[in] event an event of type parse_event_t indicating the context in + the callback function has been called + + @param[in,out] parsed the current intermediate parse result; note that + writing to this value has no effect for parse_event_t::key events + + @return Whether the JSON value which called the function during parsing + should be kept (`true`) or not (`false`). In the latter case, it is either + skipped completely or replaced by an empty discarded object. + + @sa @ref parse(std::istream&, parser_callback_t) or + @ref parse(const char*, parser_callback_t) for examples + + @since version 1.0.0 + */ + using parser_callback_t = std::function; + + + ////////////////// + // constructors // + ////////////////// + + /// @name constructors and destructors + /// Constructors of class @ref basic_json, copy/move constructor, copy + /// assignment, static functions creating objects, and the destructor. + /// @{ + + /*! + @brief create an empty value with a given type + + Create an empty JSON value with a given type. The value will be default + initialized with an empty value which depends on the type: + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` + + @param[in] value_type the type of the value to create + + @complexity Constant. + + @throw std::bad_alloc if allocation for object, array, or string value + fails + + @liveexample{The following code shows the constructor for different @ref + value_t values,basic_json__value_t} + + @sa @ref basic_json(std::nullptr_t) -- create a `null` value + @sa @ref basic_json(boolean_t value) -- create a boolean value + @sa @ref basic_json(const string_t&) -- create a string value + @sa @ref basic_json(const object_t&) -- create a object value + @sa @ref basic_json(const array_t&) -- create a array value + @sa @ref basic_json(const number_float_t) -- create a number + (floating-point) value + @sa @ref basic_json(const number_integer_t) -- create a number (integer) + value + @sa @ref basic_json(const number_unsigned_t) -- create a number (unsigned) + value + + @since version 1.0.0 + */ + basic_json(const value_t value_type) + : m_type(value_type), m_value(value_type) + { + assert_invariant(); + } + + /*! + @brief create a null object + + Create a `null` JSON value. It either takes a null pointer as parameter + (explicitly creating `null`) or no parameter (implicitly creating `null`). + The passed null pointer itself is not read -- it is only used to choose + the right constructor. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @liveexample{The following code shows the constructor with and without a + null pointer parameter.,basic_json__nullptr_t} + + @since version 1.0.0 + */ + basic_json(std::nullptr_t = nullptr) noexcept + : basic_json(value_t::null) + { + assert_invariant(); + } + + /*! + @brief create an object (explicit) + + Create an object JSON value with a given content. + + @param[in] val a value for the object + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for object value fails + + @liveexample{The following code shows the constructor with an @ref + object_t parameter.,basic_json__object_t} + + @sa @ref basic_json(const CompatibleObjectType&) -- create an object value + from a compatible STL container + + @since version 1.0.0 + */ + basic_json(const object_t& val) + : m_type(value_t::object), m_value(val) + { + assert_invariant(); + } + + /*! + @brief create an object (implicit) + + Create an object JSON value with a given content. This constructor allows + any type @a CompatibleObjectType that can be used to construct values of + type @ref object_t. + + @tparam CompatibleObjectType An object type whose `key_type` and + `value_type` is compatible to @ref object_t. Examples include `std::map`, + `std::unordered_map`, `std::multimap`, and `std::unordered_multimap` with + a `key_type` of `std::string`, and a `value_type` from which a @ref + basic_json value can be constructed. + + @param[in] val a value for the object + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for object value fails + + @liveexample{The following code shows the constructor with several + compatible object type parameters.,basic_json__CompatibleObjectType} + + @sa @ref basic_json(const object_t&) -- create an object value + + @since version 1.0.0 + */ + template::value and + std::is_constructible::value, int>::type = 0> + basic_json(const CompatibleObjectType& val) + : m_type(value_t::object) + { + using std::begin; + using std::end; + m_value.object = create(begin(val), end(val)); + assert_invariant(); + } + + /*! + @brief create an array (explicit) + + Create an array JSON value with a given content. + + @param[in] val a value for the array + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for array value fails + + @liveexample{The following code shows the constructor with an @ref array_t + parameter.,basic_json__array_t} + + @sa @ref basic_json(const CompatibleArrayType&) -- create an array value + from a compatible STL containers + + @since version 1.0.0 + */ + basic_json(const array_t& val) + : m_type(value_t::array), m_value(val) + { + assert_invariant(); + } + + /*! + @brief create an array (implicit) + + Create an array JSON value with a given content. This constructor allows + any type @a CompatibleArrayType that can be used to construct values of + type @ref array_t. + + @tparam CompatibleArrayType An object type whose `value_type` is + compatible to @ref array_t. Examples include `std::vector`, `std::deque`, + `std::list`, `std::forward_list`, `std::array`, `std::set`, + `std::unordered_set`, `std::multiset`, and `unordered_multiset` with a + `value_type` from which a @ref basic_json value can be constructed. + + @param[in] val a value for the array + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for array value fails + + @liveexample{The following code shows the constructor with several + compatible array type parameters.,basic_json__CompatibleArrayType} + + @sa @ref basic_json(const array_t&) -- create an array value + + @since version 1.0.0 + */ + template::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value and + std::is_constructible::value, int>::type = 0> + basic_json(const CompatibleArrayType& val) + : m_type(value_t::array) + { + using std::begin; + using std::end; + m_value.array = create(begin(val), end(val)); + assert_invariant(); + } + + /*! + @brief create a string (explicit) + + Create an string JSON value with a given content. + + @param[in] val a value for the string + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for string value fails + + @liveexample{The following code shows the constructor with an @ref + string_t parameter.,basic_json__string_t} + + @sa @ref basic_json(const typename string_t::value_type*) -- create a + string value from a character pointer + @sa @ref basic_json(const CompatibleStringType&) -- create a string value + from a compatible string container + + @since version 1.0.0 + */ + basic_json(const string_t& val) + : m_type(value_t::string), m_value(val) + { + assert_invariant(); + } + + /*! + @brief create a string (explicit) + + Create a string JSON value with a given content. + + @param[in] val a literal value for the string + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for string value fails + + @liveexample{The following code shows the constructor with string literal + parameter.,basic_json__string_t_value_type} + + @sa @ref basic_json(const string_t&) -- create a string value + @sa @ref basic_json(const CompatibleStringType&) -- create a string value + from a compatible string container + + @since version 1.0.0 + */ + basic_json(const typename string_t::value_type* val) + : basic_json(string_t(val)) + { + assert_invariant(); + } + + /*! + @brief create a string (implicit) + + Create a string JSON value with a given content. + + @param[in] val a value for the string + + @tparam CompatibleStringType an string type which is compatible to @ref + string_t, for instance `std::string`. + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for string value fails + + @liveexample{The following code shows the construction of a string value + from a compatible type.,basic_json__CompatibleStringType} + + @sa @ref basic_json(const string_t&) -- create a string value + @sa @ref basic_json(const typename string_t::value_type*) -- create a + string value from a character pointer + + @since version 1.0.0 + */ + template::value, int>::type = 0> + basic_json(const CompatibleStringType& val) + : basic_json(string_t(val)) + { + assert_invariant(); + } + + /*! + @brief create a boolean (explicit) + + Creates a JSON boolean type from a given value. + + @param[in] val a boolean value to store + + @complexity Constant. + + @liveexample{The example below demonstrates boolean + values.,basic_json__boolean_t} + + @since version 1.0.0 + */ + basic_json(boolean_t val) noexcept + : m_type(value_t::boolean), m_value(val) + { + assert_invariant(); + } + + /*! + @brief create an integer number (explicit) + + Create an integer number JSON value with a given content. + + @tparam T A helper type to remove this function via SFINAE in case @ref + number_integer_t is the same as `int`. In this case, this constructor + would have the same signature as @ref basic_json(const int value). Note + the helper type @a T is not visible in this constructor's interface. + + @param[in] val an integer to create a JSON number from + + @complexity Constant. + + @liveexample{The example below shows the construction of an integer + number value.,basic_json__number_integer_t} + + @sa @ref basic_json(const int) -- create a number value (integer) + @sa @ref basic_json(const CompatibleNumberIntegerType) -- create a number + value (integer) from a compatible number type + + @since version 1.0.0 + */ + template::value) and + std::is_same::value, int>::type = 0> + basic_json(const number_integer_t val) noexcept + : m_type(value_t::number_integer), m_value(val) + { + assert_invariant(); + } + + /*! + @brief create an integer number from an enum type (explicit) + + Create an integer number JSON value with a given content. + + @param[in] val an integer to create a JSON number from + + @note This constructor allows to pass enums directly to a constructor. As + C++ has no way of specifying the type of an anonymous enum explicitly, we + can only rely on the fact that such values implicitly convert to int. As + int may already be the same type of number_integer_t, we may need to + switch off the constructor @ref basic_json(const number_integer_t). + + @complexity Constant. + + @liveexample{The example below shows the construction of an integer + number value from an anonymous enum.,basic_json__const_int} + + @sa @ref basic_json(const number_integer_t) -- create a number value + (integer) + @sa @ref basic_json(const CompatibleNumberIntegerType) -- create a number + value (integer) from a compatible number type + + @since version 1.0.0 + */ + basic_json(const int val) noexcept + : m_type(value_t::number_integer), + m_value(static_cast(val)) + { + assert_invariant(); + } + + /*! + @brief create an integer number (implicit) + + Create an integer number JSON value with a given content. This constructor + allows any type @a CompatibleNumberIntegerType that can be used to + construct values of type @ref number_integer_t. + + @tparam CompatibleNumberIntegerType An integer type which is compatible to + @ref number_integer_t. Examples include the types `int`, `int32_t`, + `long`, and `short`. + + @param[in] val an integer to create a JSON number from + + @complexity Constant. + + @liveexample{The example below shows the construction of several integer + number values from compatible + types.,basic_json__CompatibleIntegerNumberType} + + @sa @ref basic_json(const number_integer_t) -- create a number value + (integer) + @sa @ref basic_json(const int) -- create a number value (integer) + + @since version 1.0.0 + */ + template::value and + std::numeric_limits::is_integer and + std::numeric_limits::is_signed, + CompatibleNumberIntegerType>::type = 0> + basic_json(const CompatibleNumberIntegerType val) noexcept + : m_type(value_t::number_integer), + m_value(static_cast(val)) + { + assert_invariant(); + } + + /*! + @brief create an unsigned integer number (explicit) + + Create an unsigned integer number JSON value with a given content. + + @tparam T helper type to compare number_unsigned_t and unsigned int (not + visible in) the interface. + + @param[in] val an integer to create a JSON number from + + @complexity Constant. + + @sa @ref basic_json(const CompatibleNumberUnsignedType) -- create a number + value (unsigned integer) from a compatible number type + + @since version 2.0.0 + */ + template::value) and + std::is_same::value, int>::type = 0> + basic_json(const number_unsigned_t val) noexcept + : m_type(value_t::number_unsigned), m_value(val) + { + assert_invariant(); + } + + /*! + @brief create an unsigned number (implicit) + + Create an unsigned number JSON value with a given content. This + constructor allows any type @a CompatibleNumberUnsignedType that can be + used to construct values of type @ref number_unsigned_t. + + @tparam CompatibleNumberUnsignedType An integer type which is compatible + to @ref number_unsigned_t. Examples may include the types `unsigned int`, + `uint32_t`, or `unsigned short`. + + @param[in] val an unsigned integer to create a JSON number from + + @complexity Constant. + + @sa @ref basic_json(const number_unsigned_t) -- create a number value + (unsigned) + + @since version 2.0.0 + */ + template::value and + std::numeric_limits::is_integer and + not std::numeric_limits::is_signed, + CompatibleNumberUnsignedType>::type = 0> + basic_json(const CompatibleNumberUnsignedType val) noexcept + : m_type(value_t::number_unsigned), + m_value(static_cast(val)) + { + assert_invariant(); + } + + /*! + @brief create a floating-point number (explicit) + + Create a floating-point number JSON value with a given content. + + @param[in] val a floating-point value to create a JSON number from + + @note [RFC 7159](http://www.rfc-editor.org/rfc/rfc7159.txt), section 6 + disallows NaN values: + > Numeric values that cannot be represented in the grammar below (such as + > Infinity and NaN) are not permitted. + In case the parameter @a val is not a number, a JSON null value is created + instead. + + @complexity Constant. + + @liveexample{The following example creates several floating-point + values.,basic_json__number_float_t} + + @sa @ref basic_json(const CompatibleNumberFloatType) -- create a number + value (floating-point) from a compatible number type + + @since version 1.0.0 + */ + basic_json(const number_float_t val) noexcept + : m_type(value_t::number_float), m_value(val) + { + // replace infinity and NAN by null + if (not std::isfinite(val)) + { + m_type = value_t::null; + m_value = json_value(); + } + + assert_invariant(); + } + + /*! + @brief create an floating-point number (implicit) + + Create an floating-point number JSON value with a given content. This + constructor allows any type @a CompatibleNumberFloatType that can be used + to construct values of type @ref number_float_t. + + @tparam CompatibleNumberFloatType A floating-point type which is + compatible to @ref number_float_t. Examples may include the types `float` + or `double`. + + @param[in] val a floating-point to create a JSON number from + + @note [RFC 7159](http://www.rfc-editor.org/rfc/rfc7159.txt), section 6 + disallows NaN values: + > Numeric values that cannot be represented in the grammar below (such as + > Infinity and NaN) are not permitted. + In case the parameter @a val is not a number, a JSON null value is + created instead. + + @complexity Constant. + + @liveexample{The example below shows the construction of several + floating-point number values from compatible + types.,basic_json__CompatibleNumberFloatType} + + @sa @ref basic_json(const number_float_t) -- create a number value + (floating-point) + + @since version 1.0.0 + */ + template::value and + std::is_floating_point::value>::type> + basic_json(const CompatibleNumberFloatType val) noexcept + : basic_json(number_float_t(val)) + { + assert_invariant(); + } + + /*! + @brief create a container (array or object) from an initializer list + + Creates a JSON value of type array or object from the passed initializer + list @a init. In case @a type_deduction is `true` (default), the type of + the JSON value to be created is deducted from the initializer list @a init + according to the following rules: + + 1. If the list is empty, an empty JSON object value `{}` is created. + 2. If the list consists of pairs whose first element is a string, a JSON + object value is created where the first elements of the pairs are + treated as keys and the second elements are as values. + 3. In all other cases, an array is created. + + The rules aim to create the best fit between a C++ initializer list and + JSON values. The rationale is as follows: + + 1. The empty initializer list is written as `{}` which is exactly an empty + JSON object. + 2. C++ has now way of describing mapped types other than to list a list of + pairs. As JSON requires that keys must be of type string, rule 2 is the + weakest constraint one can pose on initializer lists to interpret them + as an object. + 3. In all other cases, the initializer list could not be interpreted as + JSON object type, so interpreting it as JSON array type is safe. + + With the rules described above, the following JSON values cannot be + expressed by an initializer list: + + - the empty array (`[]`): use @ref array(std::initializer_list) + with an empty initializer list in this case + - arrays whose elements satisfy rule 2: use @ref + array(std::initializer_list) with the same initializer list + in this case + + @note When used without parentheses around an empty initializer list, @ref + basic_json() is called instead of this function, yielding the JSON null + value. + + @param[in] init initializer list with JSON values + + @param[in] type_deduction internal parameter; when set to `true`, the type + of the JSON value is deducted from the initializer list @a init; when set + to `false`, the type provided via @a manual_type is forced. This mode is + used by the functions @ref array(std::initializer_list) and + @ref object(std::initializer_list). + + @param[in] manual_type internal parameter; when @a type_deduction is set + to `false`, the created JSON value will use the provided type (only @ref + value_t::array and @ref value_t::object are valid); when @a type_deduction + is set to `true`, this parameter has no effect + + @throw std::domain_error if @a type_deduction is `false`, @a manual_type + is `value_t::object`, but @a init contains an element which is not a pair + whose first element is a string; example: `"cannot create object from + initializer list"` + + @complexity Linear in the size of the initializer list @a init. + + @liveexample{The example below shows how JSON values are created from + initializer lists.,basic_json__list_init_t} + + @sa @ref array(std::initializer_list) -- create a JSON array + value from an initializer list + @sa @ref object(std::initializer_list) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + basic_json(std::initializer_list init, + bool type_deduction = true, + value_t manual_type = value_t::array) + { + // check if each element is an array with two elements whose first + // element is a string + bool is_an_object = std::all_of(init.begin(), init.end(), + [](const basic_json & element) + { + return element.is_array() and element.size() == 2 and element[0].is_string(); + }); + + // adjust type if type deduction is not wanted + if (not type_deduction) + { + // if array is wanted, do not create an object though possible + if (manual_type == value_t::array) + { + is_an_object = false; + } + + // if object is wanted but impossible, throw an exception + if (manual_type == value_t::object and not is_an_object) + { + throw std::domain_error("cannot create object from initializer list"); + } + } + + if (is_an_object) + { + // the initializer list is a list of pairs -> create object + m_type = value_t::object; + m_value = value_t::object; + + std::for_each(init.begin(), init.end(), [this](const basic_json & element) + { + m_value.object->emplace(*(element[0].m_value.string), element[1]); + }); + } + else + { + // the initializer list describes an array -> create array + m_type = value_t::array; + m_value.array = create(init); + } + + assert_invariant(); + } + + /*! + @brief explicitly create an array from an initializer list + + Creates a JSON array value from a given initializer list. That is, given a + list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the + initializer list is empty, the empty array `[]` is created. + + @note This function is only needed to express two edge cases that cannot + be realized with the initializer list constructor (@ref + basic_json(std::initializer_list, bool, value_t)). These cases + are: + 1. creating an array whose elements are all pairs whose first element is a + string -- in this case, the initializer list constructor would create an + object, taking the first elements as keys + 2. creating an empty array -- passing the empty initializer list to the + initializer list constructor yields an empty object + + @param[in] init initializer list with JSON values to create an array from + (optional) + + @return JSON array value + + @complexity Linear in the size of @a init. + + @liveexample{The following code shows an example for the `array` + function.,array} + + @sa @ref basic_json(std::initializer_list, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref object(std::initializer_list) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + static basic_json array(std::initializer_list init = + std::initializer_list()) + { + return basic_json(init, false, value_t::array); + } + + /*! + @brief explicitly create an object from an initializer list + + Creates a JSON object value from a given initializer list. The initializer + lists elements must be pairs, and their first elements must be strings. If + the initializer list is empty, the empty object `{}` is created. + + @note This function is only added for symmetry reasons. In contrast to the + related function @ref array(std::initializer_list), there are + no cases which can only be expressed by this function. That is, any + initializer list @a init can also be passed to the initializer list + constructor @ref basic_json(std::initializer_list, bool, + value_t). + + @param[in] init initializer list to create an object from (optional) + + @return JSON object value + + @throw std::domain_error if @a init is not a pair whose first elements are + strings; thrown by + @ref basic_json(std::initializer_list, bool, value_t) + + @complexity Linear in the size of @a init. + + @liveexample{The following code shows an example for the `object` + function.,object} + + @sa @ref basic_json(std::initializer_list, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref array(std::initializer_list) -- create a JSON array + value from an initializer list + + @since version 1.0.0 + */ + static basic_json object(std::initializer_list init = + std::initializer_list()) + { + return basic_json(init, false, value_t::object); + } + + /*! + @brief construct an array with count copies of given value + + Constructs a JSON array value by creating @a cnt copies of a passed value. + In case @a cnt is `0`, an empty array is created. As postcondition, + `std::distance(begin(),end()) == cnt` holds. + + @param[in] cnt the number of JSON copies of @a val to create + @param[in] val the JSON value to copy + + @complexity Linear in @a cnt. + + @liveexample{The following code shows examples for the @ref + basic_json(size_type\, const basic_json&) + constructor.,basic_json__size_type_basic_json} + + @since version 1.0.0 + */ + basic_json(size_type cnt, const basic_json& val) + : m_type(value_t::array) + { + m_value.array = create(cnt, val); + assert_invariant(); + } + + /*! + @brief construct a JSON container given an iterator range + + Constructs the JSON value with the contents of the range `[first, last)`. + The semantics depends on the different types a JSON value can have: + - In case of primitive types (number, boolean, or string), @a first must + be `begin()` and @a last must be `end()`. In this case, the value is + copied. Otherwise, std::out_of_range is thrown. + - In case of structured types (array, object), the constructor behaves as + similar versions for `std::vector`. + - In case of a null type, std::domain_error is thrown. + + @tparam InputIT an input iterator type (@ref iterator or @ref + const_iterator) + + @param[in] first begin of the range to copy from (included) + @param[in] last end of the range to copy from (excluded) + + @pre Iterators @a first and @a last must be initialized. **This + precondition is enforced with an assertion.** + + @throw std::domain_error if iterators are not compatible; that is, do not + belong to the same JSON value; example: `"iterators are not compatible"` + @throw std::out_of_range if iterators are for a primitive type (number, + boolean, or string) where an out of range error can be detected easily; + example: `"iterators out of range"` + @throw std::bad_alloc if allocation for object, array, or string fails + @throw std::domain_error if called with a null value; example: `"cannot + use construct with iterators from null"` + + @complexity Linear in distance between @a first and @a last. + + @liveexample{The example below shows several ways to create JSON values by + specifying a subrange with iterators.,basic_json__InputIt_InputIt} + + @since version 1.0.0 + */ + template::value or + std::is_same::value, int>::type = 0> + basic_json(InputIT first, InputIT last) + { + assert(first.m_object != nullptr); + assert(last.m_object != nullptr); + + // make sure iterator fits the current value + if (first.m_object != last.m_object) + { + throw std::domain_error("iterators are not compatible"); + } + + // copy type from first iterator + m_type = first.m_object->m_type; + + // check if iterator range is complete for primitive values + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (not first.m_it.primitive_iterator.is_begin() or not last.m_it.primitive_iterator.is_end()) + { + throw std::out_of_range("iterators out of range"); + } + break; + } + + default: + { + break; + } + } + + switch (m_type) + { + case value_t::number_integer: + { + m_value.number_integer = first.m_object->m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_value.number_unsigned = first.m_object->m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_value.number_float = first.m_object->m_value.number_float; + break; + } + + case value_t::boolean: + { + m_value.boolean = first.m_object->m_value.boolean; + break; + } + + case value_t::string: + { + m_value = *first.m_object->m_value.string; + break; + } + + case value_t::object: + { + m_value.object = create(first.m_it.object_iterator, last.m_it.object_iterator); + break; + } + + case value_t::array: + { + m_value.array = create(first.m_it.array_iterator, last.m_it.array_iterator); + break; + } + + default: + { + throw std::domain_error("cannot use construct with iterators from " + first.m_object->type_name()); + } + } + + assert_invariant(); + } + + /*! + @brief construct a JSON value given an input stream + + @param[in,out] i stream to read a serialized JSON value from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @deprecated This constructor is deprecated and will be removed in version + 3.0.0 to unify the interface of the library. Deserialization will be + done by stream operators or by calling one of the `parse` functions, + e.g. @ref parse(std::istream&, const parser_callback_t). That is, calls + like `json j(i);` for an input stream @a i need to be replaced by + `json j = json::parse(i);`. See the example below. + + @liveexample{The example below demonstrates constructing a JSON value from + a `std::stringstream` with and without callback + function.,basic_json__istream} + + @since version 2.0.0, deprecated in version 2.0.3, to be removed in + version 3.0.0 + */ + JSON_DEPRECATED + explicit basic_json(std::istream& i, const parser_callback_t cb = nullptr) + { + *this = parser(i, cb).parse(); + assert_invariant(); + } + + /////////////////////////////////////// + // other constructors and destructor // + /////////////////////////////////////// + + /*! + @brief copy constructor + + Creates a copy of a given JSON value. + + @param[in] other the JSON value to copy + + @complexity Linear in the size of @a other. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + - As postcondition, it holds: `other == basic_json(other)`. + + @throw std::bad_alloc if allocation for object, array, or string fails. + + @liveexample{The following code shows an example for the copy + constructor.,basic_json__basic_json} + + @since version 1.0.0 + */ + basic_json(const basic_json& other) + : m_type(other.m_type) + { + // check of passed value is valid + other.assert_invariant(); + + switch (m_type) + { + case value_t::object: + { + m_value = *other.m_value.object; + break; + } + + case value_t::array: + { + m_value = *other.m_value.array; + break; + } + + case value_t::string: + { + m_value = *other.m_value.string; + break; + } + + case value_t::boolean: + { + m_value = other.m_value.boolean; + break; + } + + case value_t::number_integer: + { + m_value = other.m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_value = other.m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_value = other.m_value.number_float; + break; + } + + default: + { + break; + } + } + + assert_invariant(); + } + + /*! + @brief move constructor + + Move constructor. Constructs a JSON value with the contents of the given + value @a other using move semantics. It "steals" the resources from @a + other and leaves it as JSON null value. + + @param[in,out] other value to move to this object + + @post @a other is a JSON null value + + @complexity Constant. + + @liveexample{The code below shows the move constructor explicitly called + via std::move.,basic_json__moveconstructor} + + @since version 1.0.0 + */ + basic_json(basic_json&& other) noexcept + : m_type(std::move(other.m_type)), + m_value(std::move(other.m_value)) + { + // check that passed value is valid + other.assert_invariant(); + + // invalidate payload + other.m_type = value_t::null; + other.m_value = {}; + + assert_invariant(); + } + + /*! + @brief copy assignment + + Copy assignment operator. Copies a JSON value via the "copy and swap" + strategy: It is expressed in terms of the copy constructor, destructor, + and the swap() member function. + + @param[in] other value to copy from + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + + @liveexample{The code below shows and example for the copy assignment. It + creates a copy of value `a` which is then swapped with `b`. Finally\, the + copy of `a` (which is the null value after the swap) is + destroyed.,basic_json__copyassignment} + + @since version 1.0.0 + */ + reference& operator=(basic_json other) noexcept ( + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value + ) + { + // check that passed value is valid + other.assert_invariant(); + + using std::swap; + swap(m_type, other.m_type); + swap(m_value, other.m_value); + + assert_invariant(); + return *this; + } + + /*! + @brief destructor + + Destroys the JSON value and frees all allocated memory. + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + - All stored elements are destroyed and all memory is freed. + + @since version 1.0.0 + */ + ~basic_json() + { + assert_invariant(); + + switch (m_type) + { + case value_t::object: + { + AllocatorType alloc; + alloc.destroy(m_value.object); + alloc.deallocate(m_value.object, 1); + break; + } + + case value_t::array: + { + AllocatorType alloc; + alloc.destroy(m_value.array); + alloc.deallocate(m_value.array, 1); + break; + } + + case value_t::string: + { + AllocatorType alloc; + alloc.destroy(m_value.string); + alloc.deallocate(m_value.string, 1); + break; + } + + default: + { + // all other types need no specific destructor + break; + } + } + } + + /// @} + + public: + /////////////////////// + // object inspection // + /////////////////////// + + /// @name object inspection + /// Functions to inspect the type of a JSON value. + /// @{ + + /*! + @brief serialization + + Serialization function for JSON values. The function tries to mimic + Python's `json.dumps()` function, and currently supports its @a indent + parameter. + + @param[in] indent If indent is nonnegative, then array elements and object + members will be pretty-printed with that indent level. An indent level of + `0` will only insert newlines. `-1` (the default) selects the most compact + representation. + + @return string containing the serialization of the JSON value + + @complexity Linear. + + @liveexample{The following example shows the effect of different @a indent + parameters to the result of the serialization.,dump} + + @see https://docs.python.org/2/library/json.html#json.dump + + @since version 1.0.0 + */ + string_t dump(const int indent = -1) const + { + std::stringstream ss; + // fix locale problems + const static std::locale loc(std::locale(), new DecimalSeparator); + ss.imbue(loc); + + // 6, 15 or 16 digits of precision allows round-trip IEEE 754 + // string->float->string, string->double->string or string->long + // double->string; to be safe, we read this value from + // std::numeric_limits::digits10 + ss.precision(std::numeric_limits::digits10); + + if (indent >= 0) + { + dump(ss, true, static_cast(indent)); + } + else + { + dump(ss, false, 0); + } + + return ss.str(); + } + + /*! + @brief return the type of the JSON value (explicit) + + Return the type of the JSON value as a value from the @ref value_t + enumeration. + + @return the type of the JSON value + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `type()` for all JSON + types.,type} + + @since version 1.0.0 + */ + constexpr value_t type() const noexcept + { + return m_type; + } + + /*! + @brief return whether type is primitive + + This function returns true iff the JSON type is primitive (string, number, + boolean, or null). + + @return `true` if type is primitive (string, number, boolean, or null), + `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_primitive()` for all JSON + types.,is_primitive} + + @sa @ref is_structured() -- returns whether JSON value is structured + @sa @ref is_null() -- returns whether JSON value is `null` + @sa @ref is_string() -- returns whether JSON value is a string + @sa @ref is_boolean() -- returns whether JSON value is a boolean + @sa @ref is_number() -- returns whether JSON value is a number + + @since version 1.0.0 + */ + constexpr bool is_primitive() const noexcept + { + return is_null() or is_string() or is_boolean() or is_number(); + } + + /*! + @brief return whether type is structured + + This function returns true iff the JSON type is structured (array or + object). + + @return `true` if type is structured (array or object), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_structured()` for all JSON + types.,is_structured} + + @sa @ref is_primitive() -- returns whether value is primitive + @sa @ref is_array() -- returns whether value is an array + @sa @ref is_object() -- returns whether value is an object + + @since version 1.0.0 + */ + constexpr bool is_structured() const noexcept + { + return is_array() or is_object(); + } + + /*! + @brief return whether value is null + + This function returns true iff the JSON value is null. + + @return `true` if type is null, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_null()` for all JSON + types.,is_null} + + @since version 1.0.0 + */ + constexpr bool is_null() const noexcept + { + return m_type == value_t::null; + } + + /*! + @brief return whether value is a boolean + + This function returns true iff the JSON value is a boolean. + + @return `true` if type is boolean, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_boolean()` for all JSON + types.,is_boolean} + + @since version 1.0.0 + */ + constexpr bool is_boolean() const noexcept + { + return m_type == value_t::boolean; + } + + /*! + @brief return whether value is a number + + This function returns true iff the JSON value is a number. This includes + both integer and floating-point values. + + @return `true` if type is number (regardless whether integer, unsigned + integer or floating-type), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number()` for all JSON + types.,is_number} + + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number() const noexcept + { + return is_number_integer() or is_number_float(); + } + + /*! + @brief return whether value is an integer number + + This function returns true iff the JSON value is an integer or unsigned + integer number. This excludes floating-point values. + + @return `true` if type is an integer or unsigned integer number, `false` + otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_integer()` for all + JSON types.,is_number_integer} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number_integer() const noexcept + { + return m_type == value_t::number_integer or m_type == value_t::number_unsigned; + } + + /*! + @brief return whether value is an unsigned integer number + + This function returns true iff the JSON value is an unsigned integer + number. This excludes floating-point and (signed) integer values. + + @return `true` if type is an unsigned integer number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_unsigned()` for all + JSON types.,is_number_unsigned} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 2.0.0 + */ + constexpr bool is_number_unsigned() const noexcept + { + return m_type == value_t::number_unsigned; + } + + /*! + @brief return whether value is a floating-point number + + This function returns true iff the JSON value is a floating-point number. + This excludes integer and unsigned integer values. + + @return `true` if type is a floating-point number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_float()` for all + JSON types.,is_number_float} + + @sa @ref is_number() -- check if value is number + @sa @ref is_number_integer() -- check if value is an integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + + @since version 1.0.0 + */ + constexpr bool is_number_float() const noexcept + { + return m_type == value_t::number_float; + } + + /*! + @brief return whether value is an object + + This function returns true iff the JSON value is an object. + + @return `true` if type is object, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_object()` for all JSON + types.,is_object} + + @since version 1.0.0 + */ + constexpr bool is_object() const noexcept + { + return m_type == value_t::object; + } + + /*! + @brief return whether value is an array + + This function returns true iff the JSON value is an array. + + @return `true` if type is array, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_array()` for all JSON + types.,is_array} + + @since version 1.0.0 + */ + constexpr bool is_array() const noexcept + { + return m_type == value_t::array; + } + + /*! + @brief return whether value is a string + + This function returns true iff the JSON value is a string. + + @return `true` if type is string, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_string()` for all JSON + types.,is_string} + + @since version 1.0.0 + */ + constexpr bool is_string() const noexcept + { + return m_type == value_t::string; + } + + /*! + @brief return whether value is discarded + + This function returns true iff the JSON value was discarded during parsing + with a callback function (see @ref parser_callback_t). + + @note This function will always be `false` for JSON values after parsing. + That is, discarded values can only occur during parsing, but will be + removed when inside a structured value or replaced by null in other cases. + + @return `true` if type is discarded, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_discarded()` for all JSON + types.,is_discarded} + + @since version 1.0.0 + */ + constexpr bool is_discarded() const noexcept + { + return m_type == value_t::discarded; + } + + /*! + @brief return the type of the JSON value (implicit) + + Implicitly return the type of the JSON value as a value from the @ref + value_t enumeration. + + @return the type of the JSON value + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies the @ref value_t operator for + all JSON types.,operator__value_t} + + @since version 1.0.0 + */ + constexpr operator value_t() const noexcept + { + return m_type; + } + + /// @} + + private: + ////////////////// + // value access // + ////////////////// + + /// get an object (explicit) + template::value and + std::is_convertible::value, int>::type = 0> + T get_impl(T*) const + { + if (is_object()) + { + return T(m_value.object->begin(), m_value.object->end()); + } + else + { + throw std::domain_error("type must be object, but is " + type_name()); + } + } + + /// get an object (explicit) + object_t get_impl(object_t*) const + { + if (is_object()) + { + return *(m_value.object); + } + else + { + throw std::domain_error("type must be object, but is " + type_name()); + } + } + + /// get an array (explicit) + template::value and + not std::is_same::value and + not std::is_arithmetic::value and + not std::is_convertible::value and + not has_mapped_type::value, int>::type = 0> + T get_impl(T*) const + { + if (is_array()) + { + T to_vector; + std::transform(m_value.array->begin(), m_value.array->end(), + std::inserter(to_vector, to_vector.end()), [](basic_json i) + { + return i.get(); + }); + return to_vector; + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); + } + } + + /// get an array (explicit) + template::value and + not std::is_same::value, int>::type = 0> + std::vector get_impl(std::vector*) const + { + if (is_array()) + { + std::vector to_vector; + to_vector.reserve(m_value.array->size()); + std::transform(m_value.array->begin(), m_value.array->end(), + std::inserter(to_vector, to_vector.end()), [](basic_json i) + { + return i.get(); + }); + return to_vector; + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); + } + } + + /// get an array (explicit) + template::value and + not has_mapped_type::value, int>::type = 0> + T get_impl(T*) const + { + if (is_array()) + { + return T(m_value.array->begin(), m_value.array->end()); + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); + } + } + + /// get an array (explicit) + array_t get_impl(array_t*) const + { + if (is_array()) + { + return *(m_value.array); + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); + } + } + + /// get a string (explicit) + template::value, int>::type = 0> + T get_impl(T*) const + { + if (is_string()) + { + return *m_value.string; + } + else + { + throw std::domain_error("type must be string, but is " + type_name()); + } + } + + /// get a number (explicit) + template::value, int>::type = 0> + T get_impl(T*) const + { + switch (m_type) + { + case value_t::number_integer: + { + return static_cast(m_value.number_integer); + } + + case value_t::number_unsigned: + { + return static_cast(m_value.number_unsigned); + } + + case value_t::number_float: + { + return static_cast(m_value.number_float); + } + + default: + { + throw std::domain_error("type must be number, but is " + type_name()); + } + } + } + + /// get a boolean (explicit) + constexpr boolean_t get_impl(boolean_t*) const + { + return is_boolean() + ? m_value.boolean + : throw std::domain_error("type must be boolean, but is " + type_name()); + } + + /// get a pointer to the value (object) + object_t* get_impl_ptr(object_t*) noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (object) + constexpr const object_t* get_impl_ptr(const object_t*) const noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (array) + array_t* get_impl_ptr(array_t*) noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (array) + constexpr const array_t* get_impl_ptr(const array_t*) const noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (string) + string_t* get_impl_ptr(string_t*) noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (string) + constexpr const string_t* get_impl_ptr(const string_t*) const noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (boolean) + boolean_t* get_impl_ptr(boolean_t*) noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (boolean) + constexpr const boolean_t* get_impl_ptr(const boolean_t*) const noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (integer number) + number_integer_t* get_impl_ptr(number_integer_t*) noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (integer number) + constexpr const number_integer_t* get_impl_ptr(const number_integer_t*) const noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (unsigned number) + number_unsigned_t* get_impl_ptr(number_unsigned_t*) noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (unsigned number) + constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t*) const noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (floating-point number) + number_float_t* get_impl_ptr(number_float_t*) noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /// get a pointer to the value (floating-point number) + constexpr const number_float_t* get_impl_ptr(const number_float_t*) const noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /*! + @brief helper function to implement get_ref() + + This funcion helps to implement get_ref() without code duplication for + const and non-const overloads + + @tparam ThisType will be deduced as `basic_json` or `const basic_json` + + @throw std::domain_error if ReferenceType does not match underlying value + type of the current JSON + */ + template + static ReferenceType get_ref_impl(ThisType& obj) + { + // helper type + using PointerType = typename std::add_pointer::type; + + // delegate the call to get_ptr<>() + auto ptr = obj.template get_ptr(); + + if (ptr != nullptr) + { + return *ptr; + } + else + { + throw std::domain_error("incompatible ReferenceType for get_ref, actual type is " + + obj.type_name()); + } + } + + public: + + /// @name value access + /// Direct access to the stored value of a JSON value. + /// @{ + + /*! + @brief get a value (explicit) + + Explicit type conversion between the JSON value and a compatible value. + + @tparam ValueType non-pointer type compatible to the JSON value, for + instance `int` for JSON integer numbers, `bool` for JSON booleans, or + `std::vector` types for JSON arrays + + @return copy of the JSON value, converted to type @a ValueType + + @throw std::domain_error in case passed type @a ValueType is incompatible + to JSON; example: `"type must be object, but is null"` + + @complexity Linear in the size of the JSON value. + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,get__ValueType_const} + + @internal + The idea of using a casted null pointer to choose the correct + implementation is from . + @endinternal + + @sa @ref operator ValueType() const for implicit conversion + @sa @ref get() for pointer-member access + + @since version 1.0.0 + */ + template::value, int>::type = 0> + ValueType get() const + { + return get_impl(static_cast(nullptr)); + } + + /*! + @brief get a pointer value (explicit) + + Explicit pointer access to the internally stored JSON value. No copies are + made. + + @warning The pointer becomes invalid if the underlying JSON object + changes. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get__PointerType} + + @sa @ref get_ptr() for explicit pointer-member access + + @since version 1.0.0 + */ + template::value, int>::type = 0> + PointerType get() noexcept + { + // delegate the call to get_ptr + return get_ptr(); + } + + /*! + @brief get a pointer value (explicit) + @copydoc get() + */ + template::value, int>::type = 0> + constexpr const PointerType get() const noexcept + { + // delegate the call to get_ptr + return get_ptr(); + } + + /*! + @brief get a pointer value (implicit) + + Implicit pointer access to the internally stored JSON value. No copies are + made. + + @warning Writing data to the pointee of the result yields an undefined + state. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. Enforced by a static + assertion. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get_ptr} + + @since version 1.0.0 + */ + template::value, int>::type = 0> + PointerType get_ptr() noexcept + { + // get the type of the PointerType (remove pointer and const) + using pointee_t = typename std::remove_const::type>::type>::type; + // make sure the type matches the allowed types + static_assert( + std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + , "incompatible pointer type"); + + // delegate the call to get_impl_ptr<>() + return get_impl_ptr(static_cast(nullptr)); + } + + /*! + @brief get a pointer value (implicit) + @copydoc get_ptr() + */ + template::value and + std::is_const::type>::value, int>::type = 0> + constexpr const PointerType get_ptr() const noexcept + { + // get the type of the PointerType (remove pointer and const) + using pointee_t = typename std::remove_const::type>::type>::type; + // make sure the type matches the allowed types + static_assert( + std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + , "incompatible pointer type"); + + // delegate the call to get_impl_ptr<>() const + return get_impl_ptr(static_cast(nullptr)); + } + + /*! + @brief get a reference value (implicit) + + Implict reference access to the internally stored JSON value. No copies + are made. + + @warning Writing data to the referee of the result yields an undefined + state. + + @tparam ReferenceType reference type; must be a reference to @ref array_t, + @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or + @ref number_float_t. Enforced by static assertion. + + @return reference to the internally stored JSON value if the requested + reference type @a ReferenceType fits to the JSON value; throws + std::domain_error otherwise + + @throw std::domain_error in case passed type @a ReferenceType is + incompatible with the stored JSON value + + @complexity Constant. + + @liveexample{The example shows several calls to `get_ref()`.,get_ref} + + @since version 1.1.0 + */ + template::value, int>::type = 0> + ReferenceType get_ref() + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a reference value (implicit) + @copydoc get_ref() + */ + template::value and + std::is_const::type>::value, int>::type = 0> + ReferenceType get_ref() const + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a value (implicit) + + Implicit type conversion between the JSON value and a compatible value. + The call is realized by calling @ref get() const. + + @tparam ValueType non-pointer type compatible to the JSON value, for + instance `int` for JSON integer numbers, `bool` for JSON booleans, or + `std::vector` types for JSON arrays. The character type of @ref string_t + as well as an initializer list of this type is excluded to avoid + ambiguities as these types implicitly convert to `std::string`. + + @return copy of the JSON value, converted to type @a ValueType + + @throw std::domain_error in case passed type @a ValueType is incompatible + to JSON, thrown by @ref get() const + + @complexity Linear in the size of the JSON value. + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,operator__ValueType} + + @since version 1.0.0 + */ + template < typename ValueType, typename std::enable_if < + not std::is_pointer::value and + not std::is_same::value +#ifndef _MSC_VER // Fix for issue #167 operator<< abiguity under VS2015 + and not std::is_same>::value +#endif + , int >::type = 0 > + operator ValueType() const + { + // delegate the call to get<>() const + return get(); + } + + /// @} + + + //////////////////// + // element access // + //////////////////// + + /// @name element access + /// Access to the JSON value. + /// @{ + + /*! + @brief access specified array element with bounds checking + + Returns a reference to the element at specified location @a idx, with + bounds checking. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw std::domain_error if the JSON value is not an array; example: + `"cannot use at() with string"` + @throw std::out_of_range if the index @a idx is out of range of the array; + that is, `idx >= size()`; example: `"array index 7 is out of range"` + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read and + written using `at()`.,at__size_type} + + @since version 1.0.0 + */ + reference at(size_type idx) + { + // at only works for arrays + if (is_array()) + { + try + { + return m_value.array->at(idx); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); + } + } + else + { + throw std::domain_error("cannot use at() with " + type_name()); + } + } + + /*! + @brief access specified array element with bounds checking + + Returns a const reference to the element at specified location @a idx, + with bounds checking. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw std::domain_error if the JSON value is not an array; example: + `"cannot use at() with string"` + @throw std::out_of_range if the index @a idx is out of range of the array; + that is, `idx >= size()`; example: `"array index 7 is out of range"` + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read using + `at()`.,at__size_type_const} + + @since version 1.0.0 + */ + const_reference at(size_type idx) const + { + // at only works for arrays + if (is_array()) + { + try + { + return m_value.array->at(idx); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); + } + } + else + { + throw std::domain_error("cannot use at() with " + type_name()); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a reference to the element at with specified key @a key, with + bounds checking. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw std::domain_error if the JSON value is not an object; example: + `"cannot use at() with boolean"` + @throw std::out_of_range if the key @a key is is not stored in the object; + that is, `find(key) == end()`; example: `"key "the fast" not found"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using `at()`.,at__object_t_key_type} + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + reference at(const typename object_t::key_type& key) + { + // at only works for objects + if (is_object()) + { + try + { + return m_value.object->at(key); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("key '" + key + "' not found"); + } + } + else + { + throw std::domain_error("cannot use at() with " + type_name()); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a const reference to the element at with specified key @a key, + with bounds checking. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @throw std::domain_error if the JSON value is not an object; example: + `"cannot use at() with boolean"` + @throw std::out_of_range if the key @a key is is not stored in the object; + that is, `find(key) == end()`; example: `"key "the fast" not found"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + `at()`.,at__object_t_key_type_const} + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + const_reference at(const typename object_t::key_type& key) const + { + // at only works for objects + if (is_object()) + { + try + { + return m_value.object->at(key); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("key '" + key + "' not found"); + } + } + else + { + throw std::domain_error("cannot use at() with " + type_name()); + } + } + + /*! + @brief access specified array element + + Returns a reference to the element at specified location @a idx. + + @note If @a idx is beyond the range of the array (i.e., `idx >= size()`), + then the array is silently filled up with `null` values to make `idx` a + valid reference to the last stored element. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw std::domain_error if JSON is not an array or null; example: + `"cannot use operator[] with string"` + + @complexity Constant if @a idx is in the range of the array. Otherwise + linear in `idx - size()`. + + @liveexample{The example below shows how array elements can be read and + written using `[]` operator. Note the addition of `null` + values.,operatorarray__size_type} + + @since version 1.0.0 + */ + reference operator[](size_type idx) + { + // implicitly convert null value to an empty array + if (is_null()) + { + m_type = value_t::array; + m_value.array = create(); + assert_invariant(); + } + + // operator[] only works for arrays + if (is_array()) + { + // fill up array with null values if given idx is outside range + if (idx >= m_value.array->size()) + { + m_value.array->insert(m_value.array->end(), + idx - m_value.array->size() + 1, + basic_json()); + } + + return m_value.array->operator[](idx); + } + else + { + throw std::domain_error("cannot use operator[] with " + type_name()); + } + } + + /*! + @brief access specified array element + + Returns a const reference to the element at specified location @a idx. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw std::domain_error if JSON is not an array; example: `"cannot use + operator[] with null"` + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read using + the `[]` operator.,operatorarray__size_type_const} + + @since version 1.0.0 + */ + const_reference operator[](size_type idx) const + { + // const operator[] only works for arrays + if (is_array()) + { + return m_value.array->operator[](idx); + } + else + { + throw std::domain_error("cannot use operator[] with " + type_name()); + } + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw std::domain_error if JSON is not an object or null; example: + `"cannot use operator[] with string"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + reference operator[](const typename object_t::key_type& key) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + // operator[] only works for objects + if (is_object()) + { + return m_value.object->operator[](key); + } + else + { + throw std::domain_error("cannot use operator[] with " + type_name()); + } + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @pre The element with key @a key must exist. **This precondition is + enforced with an assertion.** + + @throw std::domain_error if JSON is not an object; example: `"cannot use + operator[] with null"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + const_reference operator[](const typename object_t::key_type& key) const + { + // const operator[] only works for objects + if (is_object()) + { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + else + { + throw std::domain_error("cannot use operator[] with " + type_name()); + } + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw std::domain_error if JSON is not an object or null; example: + `"cannot use operator[] with string"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + template + reference operator[](T * (&key)[n]) + { + return operator[](static_cast(key)); + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @note This function is required for compatibility reasons with Clang. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @throw std::domain_error if JSON is not an object; example: `"cannot use + operator[] with null"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + template + const_reference operator[](T * (&key)[n]) const + { + return operator[](static_cast(key)); + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw std::domain_error if JSON is not an object or null; example: + `"cannot use operator[] with string"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template + reference operator[](T* key) + { + // implicitly convert null to object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // at only works for objects + if (is_object()) + { + return m_value.object->operator[](key); + } + else + { + throw std::domain_error("cannot use operator[] with " + type_name()); + } + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @pre The element with key @a key must exist. **This precondition is + enforced with an assertion.** + + @throw std::domain_error if JSON is not an object; example: `"cannot use + operator[] with null"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template + const_reference operator[](T* key) const + { + // at only works for objects + if (is_object()) + { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + else + { + throw std::domain_error("cannot use operator[] with " + type_name()); + } + } + + /*! + @brief access specified object element with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(key); + } catch(std::out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const typename object_t::key_type&), this function + does not throw if the given key @a key was not found. + + @note Unlike @ref operator[](const typename object_t::key_type& key), this + function does not implicitly add an element to the position defined by @a + key. This function is furthermore also applicable to const objects. + + @param[in] key key of the element to access + @param[in] default_value the value to return if @a key is not found + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw std::domain_error if JSON is not an object; example: `"cannot use + value() with null"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + + @since version 1.0.0 + */ + template::value, int>::type = 0> + ValueType value(const typename object_t::key_type& key, ValueType default_value) const + { + // at only works for objects + if (is_object()) + { + // if key is found, return value and given default value otherwise + const auto it = find(key); + if (it != end()) + { + return *it; + } + else + { + return default_value; + } + } + else + { + throw std::domain_error("cannot use value() with " + type_name()); + } + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const typename object_t::key_type&, ValueType) const + */ + string_t value(const typename object_t::key_type& key, const char* default_value) const + { + return value(key, string_t(default_value)); + } + + /*! + @brief access specified object element via JSON Pointer with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(ptr); + } catch(std::out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const json_pointer&), this function does not throw + if the given key @a key was not found. + + @param[in] ptr a JSON pointer to the element to access + @param[in] default_value the value to return if @a ptr found no value + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw std::domain_error if JSON is not an object; example: `"cannot use + value() with null"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value_ptr} + + @sa @ref operator[](const json_pointer&) for unchecked access by reference + + @since version 2.0.2 + */ + template::value, int>::type = 0> + ValueType value(const json_pointer& ptr, ValueType default_value) const + { + // at only works for objects + if (is_object()) + { + // if pointer resolves a value, return it or use default value + try + { + return ptr.get_checked(this); + } + catch (std::out_of_range&) + { + return default_value; + } + } + else + { + throw std::domain_error("cannot use value() with " + type_name()); + } + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const json_pointer&, ValueType) const + */ + string_t value(const json_pointer& ptr, const char* default_value) const + { + return value(ptr, string_t(default_value)); + } + + /*! + @brief access the first element + + Returns a reference to the first element in the container. For a JSON + container `c`, the expression `c.front()` is equivalent to `*c.begin()`. + + @return In case of a structured type (array or object), a reference to the + first element is returned. In cast of number, string, or boolean values, a + reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, **guarded by + assertions**). + @post The JSON value remains unchanged. + + @throw std::out_of_range when called on `null` value + + @liveexample{The following code shows an example for `front()`.,front} + + @sa @ref back() -- access the last element + + @since version 1.0.0 + */ + reference front() + { + return *begin(); + } + + /*! + @copydoc basic_json::front() + */ + const_reference front() const + { + return *cbegin(); + } + + /*! + @brief access the last element + + Returns a reference to the last element in the container. For a JSON + container `c`, the expression `c.back()` is equivalent to + @code {.cpp} + auto tmp = c.end(); + --tmp; + return *tmp; + @endcode + + @return In case of a structured type (array or object), a reference to the + last element is returned. In cast of number, string, or boolean values, a + reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, **guarded by + assertions**). + @post The JSON value remains unchanged. + + @throw std::out_of_range when called on `null` value. + + @liveexample{The following code shows an example for `back()`.,back} + + @sa @ref front() -- access the first element + + @since version 1.0.0 + */ + reference back() + { + auto tmp = end(); + --tmp; + return *tmp; + } + + /*! + @copydoc basic_json::back() + */ + const_reference back() const + { + auto tmp = cend(); + --tmp; + return *tmp; + } + + /*! + @brief remove element given an iterator + + Removes the element specified by iterator @a pos. The iterator @a pos must + be valid and dereferenceable. Thus the `end()` iterator (which is valid, + but is not dereferenceable) cannot be used as a value for @a pos. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] pos iterator to the element to remove + @return Iterator following the last removed element. If the iterator @a + pos refers to the last element, the `end()` iterator is returned. + + @tparam IteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw std::domain_error if called on a `null` value; example: `"cannot + use erase() with null"` + @throw std::domain_error if called on an iterator which does not belong to + the current JSON value; example: `"iterator does not fit current value"` + @throw std::out_of_range if called on a primitive type with invalid + iterator (i.e., any iterator which is not `begin()`); example: `"iterator + out of range"` + + @complexity The complexity depends on the type: + - objects: amortized constant + - arrays: linear in distance between pos and the end of the container + - strings: linear in the length of the string + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType} + + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template::value or + std::is_same::value, int>::type + = 0> + IteratorType erase(IteratorType pos) + { + // make sure iterator fits the current value + if (this != pos.m_object) + { + throw std::domain_error("iterator does not fit current value"); + } + + IteratorType result = end(); + + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (not pos.m_it.primitive_iterator.is_begin()) + { + throw std::out_of_range("iterator out of range"); + } + + if (is_string()) + { + AllocatorType alloc; + alloc.destroy(m_value.string); + alloc.deallocate(m_value.string, 1); + m_value.string = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); + break; + } + + default: + { + throw std::domain_error("cannot use erase() with " + type_name()); + } + } + + return result; + } + + /*! + @brief remove elements given an iterator range + + Removes the element specified by the range `[first; last)`. The iterator + @a first does not need to be dereferenceable if `first == last`: erasing + an empty range is a no-op. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] first iterator to the beginning of the range to remove + @param[in] last iterator past the end of the range to remove + @return Iterator following the last removed element. If the iterator @a + second refers to the last element, the `end()` iterator is returned. + + @tparam IteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw std::domain_error if called on a `null` value; example: `"cannot + use erase() with null"` + @throw std::domain_error if called on iterators which does not belong to + the current JSON value; example: `"iterators do not fit current value"` + @throw std::out_of_range if called on a primitive type with invalid + iterators (i.e., if `first != begin()` and `last != end()`); example: + `"iterators out of range"` + + @complexity The complexity depends on the type: + - objects: `log(size()) + std::distance(first, last)` + - arrays: linear in the distance between @a first and @a last, plus linear + in the distance between @a last and end of the container + - strings: linear in the length of the string + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType_IteratorType} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template::value or + std::is_same::value, int>::type + = 0> + IteratorType erase(IteratorType first, IteratorType last) + { + // make sure iterator fits the current value + if (this != first.m_object or this != last.m_object) + { + throw std::domain_error("iterators do not fit current value"); + } + + IteratorType result = end(); + + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (not first.m_it.primitive_iterator.is_begin() or not last.m_it.primitive_iterator.is_end()) + { + throw std::out_of_range("iterators out of range"); + } + + if (is_string()) + { + AllocatorType alloc; + alloc.destroy(m_value.string); + alloc.deallocate(m_value.string, 1); + m_value.string = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + default: + { + throw std::domain_error("cannot use erase() with " + type_name()); + } + } + + return result; + } + + /*! + @brief remove element from a JSON object given a key + + Removes elements from a JSON object with the key value @a key. + + @param[in] key value of the elements to remove + + @return Number of elements removed. If @a ObjectType is the default + `std::map` type, the return value will always be `0` (@a key was not + found) or `1` (@a key was found). + + @post References and iterators to the erased elements are invalidated. + Other references and iterators are not affected. + + @throw std::domain_error when called on a type other than JSON object; + example: `"cannot use erase() with null"` + + @complexity `log(size()) + count(key)` + + @liveexample{The example shows the effect of `erase()`.,erase__key_type} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + size_type erase(const typename object_t::key_type& key) + { + // this erase only works for objects + if (is_object()) + { + return m_value.object->erase(key); + } + else + { + throw std::domain_error("cannot use erase() with " + type_name()); + } + } + + /*! + @brief remove element from a JSON array given an index + + Removes element from a JSON array at the index @a idx. + + @param[in] idx index of the element to remove + + @throw std::domain_error when called on a type other than JSON array; + example: `"cannot use erase() with null"` + @throw std::out_of_range when `idx >= size()`; example: `"array index 17 + is out of range"` + + @complexity Linear in distance between @a idx and the end of the container. + + @liveexample{The example shows the effect of `erase()`.,erase__size_type} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + + @since version 1.0.0 + */ + void erase(const size_type idx) + { + // this erase only works for arrays + if (is_array()) + { + if (idx >= size()) + { + throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); + } + + m_value.array->erase(m_value.array->begin() + static_cast(idx)); + } + else + { + throw std::domain_error("cannot use erase() with " + type_name()); + } + } + + /// @} + + + //////////// + // lookup // + //////////// + + /// @name lookup + /// @{ + + /*! + @brief find an element in a JSON object + + Finds an element in a JSON object with key equivalent to @a key. If the + element is not found or the JSON value is not an object, end() is + returned. + + @param[in] key key value of the element to search for + + @return Iterator to an element with key equivalent to @a key. If no such + element is found, past-the-end (see end()) iterator is returned. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `find()` is used.,find__key_type} + + @since version 1.0.0 + */ + iterator find(typename object_t::key_type key) + { + auto result = end(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(key); + } + + return result; + } + + /*! + @brief find an element in a JSON object + @copydoc find(typename object_t::key_type) + */ + const_iterator find(typename object_t::key_type key) const + { + auto result = cend(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(key); + } + + return result; + } + + /*! + @brief returns the number of occurrences of a key in a JSON object + + Returns the number of elements with key @a key. If ObjectType is the + default `std::map` type, the return value will always be `0` (@a key was + not found) or `1` (@a key was found). + + @param[in] key key value of the element to count + + @return Number of elements with key @a key. If the JSON value is not an + object, the return value will be `0`. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `count()` is used.,count} + + @since version 1.0.0 + */ + size_type count(typename object_t::key_type key) const + { + // return 0 for all nonobject types + return is_object() ? m_value.object->count(key) : 0; + } + + /// @} + + + /////////////// + // iterators // + /////////////// + + /// @name iterators + /// @{ + + /*! + @brief returns an iterator to the first element + + Returns an iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `begin()`.,begin} + + @sa @ref cbegin() -- returns a const iterator to the beginning + @sa @ref end() -- returns an iterator to the end + @sa @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + iterator begin() noexcept + { + iterator result(this); + result.set_begin(); + return result; + } + + /*! + @copydoc basic_json::cbegin() + */ + const_iterator begin() const noexcept + { + return cbegin(); + } + + /*! + @brief returns a const iterator to the first element + + Returns a const iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).begin()`. + + @liveexample{The following code shows an example for `cbegin()`.,cbegin} + + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref end() -- returns an iterator to the end + @sa @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + const_iterator cbegin() const noexcept + { + const_iterator result(this); + result.set_begin(); + return result; + } + + /*! + @brief returns an iterator to one past the last element + + Returns an iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `end()`.,end} + + @sa @ref cend() -- returns a const iterator to the end + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + iterator end() noexcept + { + iterator result(this); + result.set_end(); + return result; + } + + /*! + @copydoc basic_json::cend() + */ + const_iterator end() const noexcept + { + return cend(); + } + + /*! + @brief returns a const iterator to one past the last element + + Returns a const iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).end()`. + + @liveexample{The following code shows an example for `cend()`.,cend} + + @sa @ref end() -- returns an iterator to the end + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + const_iterator cend() const noexcept + { + const_iterator result(this); + result.set_end(); + return result; + } + + /*! + @brief returns an iterator to the reverse-beginning + + Returns an iterator to the reverse-beginning; that is, the last element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(end())`. + + @liveexample{The following code shows an example for `rbegin()`.,rbegin} + + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + reverse_iterator rbegin() noexcept + { + return reverse_iterator(end()); + } + + /*! + @copydoc basic_json::crbegin() + */ + const_reverse_iterator rbegin() const noexcept + { + return crbegin(); + } + + /*! + @brief returns an iterator to the reverse-end + + Returns an iterator to the reverse-end; that is, one before the first + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(begin())`. + + @liveexample{The following code shows an example for `rend()`.,rend} + + @sa @ref crend() -- returns a const reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + reverse_iterator rend() noexcept + { + return reverse_iterator(begin()); + } + + /*! + @copydoc basic_json::crend() + */ + const_reverse_iterator rend() const noexcept + { + return crend(); + } + + /*! + @brief returns a const reverse iterator to the last element + + Returns a const iterator to the reverse-beginning; that is, the last + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rbegin()`. + + @liveexample{The following code shows an example for `crbegin()`.,crbegin} + + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + const_reverse_iterator crbegin() const noexcept + { + return const_reverse_iterator(cend()); + } + + /*! + @brief returns a const reverse iterator to one before the first + + Returns a const reverse iterator to the reverse-end; that is, one before + the first element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rend()`. + + @liveexample{The following code shows an example for `crend()`.,crend} + + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + const_reverse_iterator crend() const noexcept + { + return const_reverse_iterator(cbegin()); + } + + private: + // forward declaration + template class iteration_proxy; + + public: + /*! + @brief wrapper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. + + @note The name of this function is not yet final and may change in the + future. + */ + static iteration_proxy iterator_wrapper(reference cont) + { + return iteration_proxy(cont); + } + + /*! + @copydoc iterator_wrapper(reference) + */ + static iteration_proxy iterator_wrapper(const_reference cont) + { + return iteration_proxy(cont); + } + + /// @} + + + ////////////// + // capacity // + ////////////// + + /// @name capacity + /// @{ + + /*! + @brief checks whether the container is empty + + Checks if a JSON value has no elements. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `true` + boolean | `false` + string | `false` + number | `false` + object | result of function `object_t::empty()` + array | result of function `array_t::empty()` + + @note This function does not return whether a string stored as JSON value + is empty - it returns whether the JSON container itself is empty which is + false in the case of a string. + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `empty()` functions have constant + complexity. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `begin() == end()`. + + @liveexample{The following code uses `empty()` to check if a JSON + object contains any elements.,empty} + + @sa @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + bool empty() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return true; + } + + case value_t::array: + { + // delegate call to array_t::empty() + return m_value.array->empty(); + } + + case value_t::object: + { + // delegate call to object_t::empty() + return m_value.object->empty(); + } + + default: + { + // all other types are nonempty + return false; + } + } + } + + /*! + @brief returns the number of elements + + Returns the number of elements in a JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` + boolean | `1` + string | `1` + number | `1` + object | result of function object_t::size() + array | result of function array_t::size() + + @note This function does not return the length of a string stored as JSON + value - it returns the number of elements in the JSON value which is 1 in + the case of a string. + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their size() functions have constant + complexity. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `std::distance(begin(), end())`. + + @liveexample{The following code calls `size()` on the different value + types.,size} + + @sa @ref empty() -- checks whether the container is empty + @sa @ref max_size() -- returns the maximal number of elements + + @since version 1.0.0 + */ + size_type size() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return 0; + } + + case value_t::array: + { + // delegate call to array_t::size() + return m_value.array->size(); + } + + case value_t::object: + { + // delegate call to object_t::size() + return m_value.object->size(); + } + + default: + { + // all other types have size 1 + return 1; + } + } + } + + /*! + @brief returns the maximum possible number of elements + + Returns the maximum number of elements a JSON value is able to hold due to + system or library implementation limitations, i.e. `std::distance(begin(), + end())` for the JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` (same as `size()`) + boolean | `1` (same as `size()`) + string | `1` (same as `size()`) + number | `1` (same as `size()`) + object | result of function `object_t::max_size()` + array | result of function `array_t::max_size()` + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `max_size()` functions have constant + complexity. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of returning `b.size()` where `b` is the largest + possible JSON value. + + @liveexample{The following code calls `max_size()` on the different value + types. Note the output is implementation specific.,max_size} + + @sa @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + size_type max_size() const noexcept + { + switch (m_type) + { + case value_t::array: + { + // delegate call to array_t::max_size() + return m_value.array->max_size(); + } + + case value_t::object: + { + // delegate call to object_t::max_size() + return m_value.object->max_size(); + } + + default: + { + // all other types have max_size() == size() + return size(); + } + } + } + + /// @} + + + /////////////// + // modifiers // + /////////////// + + /// @name modifiers + /// @{ + + /*! + @brief clears the contents + + Clears the content of a JSON value and resets it to the default value as + if @ref basic_json(value_t) would have been called: + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` + + @note Floating-point numbers are set to `0.0` which will be serialized to + `0`. The vale type remains @ref number_float_t. + + @complexity Linear in the size of the JSON value. + + @liveexample{The example below shows the effect of `clear()` to different + JSON types.,clear} + + @since version 1.0.0 + */ + void clear() noexcept + { + switch (m_type) + { + case value_t::number_integer: + { + m_value.number_integer = 0; + break; + } + + case value_t::number_unsigned: + { + m_value.number_unsigned = 0; + break; + } + + case value_t::number_float: + { + m_value.number_float = 0.0; + break; + } + + case value_t::boolean: + { + m_value.boolean = false; + break; + } + + case value_t::string: + { + m_value.string->clear(); + break; + } + + case value_t::array: + { + m_value.array->clear(); + break; + } + + case value_t::object: + { + m_value.object->clear(); + break; + } + + default: + { + break; + } + } + } + + /*! + @brief add an object to an array + + Appends the given element @a val to the end of the JSON value. If the + function is called on a JSON null value, an empty array is created before + appending @a val. + + @param[in] val the value to add to the JSON array + + @throw std::domain_error when called on a type other than JSON array or + null; example: `"cannot use push_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON array. Note how the `null` value was silently + converted to a JSON array.,push_back} + + @since version 1.0.0 + */ + void push_back(basic_json&& val) + { + // push_back only works for null objects or arrays + if (not(is_null() or is_array())) + { + throw std::domain_error("cannot use push_back() with " + type_name()); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (move semantics) + m_value.array->push_back(std::move(val)); + // invalidate object + val.m_type = value_t::null; + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(basic_json&& val) + { + push_back(std::move(val)); + return *this; + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + void push_back(const basic_json& val) + { + // push_back only works for null objects or arrays + if (not(is_null() or is_array())) + { + throw std::domain_error("cannot use push_back() with " + type_name()); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array + m_value.array->push_back(val); + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(const basic_json& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + Inserts the given element @a val to the JSON object. If the function is + called on a JSON null value, an empty object is created before inserting + @a val. + + @param[in] val the value to add to the JSON object + + @throw std::domain_error when called on a type other than JSON object or + null; example: `"cannot use push_back() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON object. Note how the `null` value was silently + converted to a JSON object.,push_back__object_t__value} + + @since version 1.0.0 + */ + void push_back(const typename object_t::value_type& val) + { + // push_back only works for null objects or objects + if (not(is_null() or is_object())) + { + throw std::domain_error("cannot use push_back() with " + type_name()); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array + m_value.object->insert(val); + } + + /*! + @brief add an object to an object + @copydoc push_back(const typename object_t::value_type&) + */ + reference operator+=(const typename object_t::value_type& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + This function allows to use `push_back` with an initializer list. In case + + 1. the current value is an object, + 2. the initializer list @a init contains only two elements, and + 3. the first element of @a init is a string, + + @a init is converted into an object element and added using + @ref push_back(const typename object_t::value_type&). Otherwise, @a init + is converted to a JSON value and added using @ref push_back(basic_json&&). + + @param init an initializer list + + @complexity Linear in the size of the initializer list @a init. + + @note This function is required to resolve an ambiguous overload error, + because pairs like `{"key", "value"}` can be both interpreted as + `object_t::value_type` or `std::initializer_list`, see + https://github.com/nlohmann/json/issues/235 for more information. + + @liveexample{The example shows how initializer lists are treated as + objects when possible.,push_back__initializer_list} + */ + void push_back(std::initializer_list init) + { + if (is_object() and init.size() == 2 and init.begin()->is_string()) + { + const string_t key = *init.begin(); + push_back(typename object_t::value_type(key, *(init.begin() + 1))); + } + else + { + push_back(basic_json(init)); + } + } + + /*! + @brief add an object to an object + @copydoc push_back(std::initializer_list) + */ + reference operator+=(std::initializer_list init) + { + push_back(init); + return *this; + } + + /*! + @brief inserts element + + Inserts element @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] val element to insert + @return iterator pointing to the inserted @a val. + + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` + + @complexity Constant plus linear in the distance between pos and end of the + container. + + @liveexample{The example shows how `insert()` is used.,insert} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const basic_json& val) + { + // insert only works for arrays + if (is_array()) + { + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + throw std::domain_error("iterator does not fit current value"); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, val); + return result; + } + else + { + throw std::domain_error("cannot use insert() with " + type_name()); + } + } + + /*! + @brief inserts element + @copydoc insert(const_iterator, const basic_json&) + */ + iterator insert(const_iterator pos, basic_json&& val) + { + return insert(pos, val); + } + + /*! + @brief inserts elements + + Inserts @a cnt copies of @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] cnt number of copies of @a val to insert + @param[in] val element to insert + @return iterator pointing to the first element inserted, or @a pos if + `cnt==0` + + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` + + @complexity Linear in @a cnt plus linear in the distance between @a pos + and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__count} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, size_type cnt, const basic_json& val) + { + // insert only works for arrays + if (is_array()) + { + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + throw std::domain_error("iterator does not fit current value"); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); + return result; + } + else + { + throw std::domain_error("cannot use insert() with " + type_name()); + } + } + + /*! + @brief inserts elements + + Inserts elements from range `[first, last)` before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` + @throw std::domain_error if @a first and @a last do not belong to the same + JSON value; example: `"iterators do not fit"` + @throw std::domain_error if @a first or @a last are iterators into + container for which insert is called; example: `"passed iterators may not + belong to container"` + + @return iterator pointing to the first element inserted, or @a pos if + `first==last` + + @complexity Linear in `std::distance(first, last)` plus linear in the + distance between @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__range} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const_iterator first, const_iterator last) + { + // insert only works for arrays + if (not is_array()) + { + throw std::domain_error("cannot use insert() with " + type_name()); + } + + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + throw std::domain_error("iterator does not fit current value"); + } + + // check if range iterators belong to the same JSON object + if (first.m_object != last.m_object) + { + throw std::domain_error("iterators do not fit"); + } + + if (first.m_object == this or last.m_object == this) + { + throw std::domain_error("passed iterators may not belong to container"); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert( + pos.m_it.array_iterator, + first.m_it.array_iterator, + last.m_it.array_iterator); + return result; + } + + /*! + @brief inserts elements + + Inserts elements from initializer list @a ilist before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] ilist initializer list to insert the values from + + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` + + @return iterator pointing to the first element inserted, or @a pos if + `ilist` is empty + + @complexity Linear in `ilist.size()` plus linear in the distance between + @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__ilist} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, std::initializer_list ilist) + { + // insert only works for arrays + if (not is_array()) + { + throw std::domain_error("cannot use insert() with " + type_name()); + } + + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + throw std::domain_error("iterator does not fit current value"); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist); + return result; + } + + /*! + @brief exchanges the values + + Exchanges the contents of the JSON value with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other JSON value to exchange the contents with + + @complexity Constant. + + @liveexample{The example below shows how JSON values can be swapped with + `swap()`.,swap__reference} + + @since version 1.0.0 + */ + void swap(reference other) noexcept ( + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value + ) + { + std::swap(m_type, other.m_type); + std::swap(m_value, other.m_value); + assert_invariant(); + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON array with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other array to exchange the contents with + + @throw std::domain_error when JSON value is not an array; example: `"cannot + use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how arrays can be swapped with + `swap()`.,swap__array_t} + + @since version 1.0.0 + */ + void swap(array_t& other) + { + // swap only works for arrays + if (is_array()) + { + std::swap(*(m_value.array), other); + } + else + { + throw std::domain_error("cannot use swap() with " + type_name()); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON object with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other object to exchange the contents with + + @throw std::domain_error when JSON value is not an object; example: + `"cannot use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how objects can be swapped with + `swap()`.,swap__object_t} + + @since version 1.0.0 + */ + void swap(object_t& other) + { + // swap only works for objects + if (is_object()) + { + std::swap(*(m_value.object), other); + } + else + { + throw std::domain_error("cannot use swap() with " + type_name()); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON string with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other string to exchange the contents with + + @throw std::domain_error when JSON value is not a string; example: `"cannot + use swap() with boolean"` + + @complexity Constant. + + @liveexample{The example below shows how strings can be swapped with + `swap()`.,swap__string_t} + + @since version 1.0.0 + */ + void swap(string_t& other) + { + // swap only works for strings + if (is_string()) + { + std::swap(*(m_value.string), other); + } + else + { + throw std::domain_error("cannot use swap() with " + type_name()); + } + } + + /// @} + + + ////////////////////////////////////////// + // lexicographical comparison operators // + ////////////////////////////////////////// + + /// @name lexicographical comparison operators + /// @{ + + private: + /*! + @brief comparison operator for JSON types + + Returns an ordering that is similar to Python: + - order: null < boolean < number < object < array < string + - furthermore, each type is not smaller than itself + + @since version 1.0.0 + */ + friend bool operator<(const value_t lhs, const value_t rhs) noexcept + { + static constexpr std::array order = {{ + 0, // null + 3, // object + 4, // array + 5, // string + 1, // boolean + 2, // integer + 2, // unsigned + 2, // float + } + }; + + // discarded values are not comparable + if (lhs == value_t::discarded or rhs == value_t::discarded) + { + return false; + } + + return order[static_cast(lhs)] < order[static_cast(rhs)]; + } + + public: + /*! + @brief comparison: equal + + Compares two JSON values for equality according to the following rules: + - Two JSON values are equal if (1) they are from the same type and (2) + their stored values are the same. + - Integer and floating-point numbers are automatically converted before + comparison. Floating-point numbers are compared indirectly: two + floating-point numbers `f1` and `f2` are considered equal if neither + `f1 > f2` nor `f2 > f1` holds. + - Two JSON null values are equal. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are equal + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__equal} + + @since version 1.0.0 + */ + friend bool operator==(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + { + return *lhs.m_value.array == *rhs.m_value.array; + } + case value_t::object: + { + return *lhs.m_value.object == *rhs.m_value.object; + } + case value_t::null: + { + return true; + } + case value_t::string: + { + return *lhs.m_value.string == *rhs.m_value.string; + } + case value_t::boolean: + { + return lhs.m_value.boolean == rhs.m_value.boolean; + } + case value_t::number_integer: + { + return lhs.m_value.number_integer == rhs.m_value.number_integer; + } + case value_t::number_unsigned: + { + return lhs.m_value.number_unsigned == rhs.m_value.number_unsigned; + } + case value_t::number_float: + { + return lhs.m_value.number_float == rhs.m_value.number_float; + } + default: + { + return false; + } + } + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) + { + return lhs.m_value.number_float == static_cast(rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) + { + return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer; + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned); + } + + return false; + } + + /*! + @brief comparison: equal + + The functions compares the given JSON value against a null pointer. As the + null pointer can be used to initialize a JSON value to null, a comparison + of JSON value @a v with a null pointer should be equivalent to call + `v.is_null()`. + + @param[in] v JSON value to consider + @return whether @a v is null + + @complexity Constant. + + @liveexample{The example compares several JSON types to the null pointer. + ,operator__equal__nullptr_t} + + @since version 1.0.0 + */ + friend bool operator==(const_reference v, std::nullptr_t) noexcept + { + return v.is_null(); + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, std::nullptr_t) + */ + friend bool operator==(std::nullptr_t, const_reference v) noexcept + { + return v.is_null(); + } + + /*! + @brief comparison: not equal + + Compares two JSON values for inequality by calculating `not (lhs == rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are not equal + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__notequal} + + @since version 1.0.0 + */ + friend bool operator!=(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs == rhs); + } + + /*! + @brief comparison: not equal + + The functions compares the given JSON value against a null pointer. As the + null pointer can be used to initialize a JSON value to null, a comparison + of JSON value @a v with a null pointer should be equivalent to call + `not v.is_null()`. + + @param[in] v JSON value to consider + @return whether @a v is not null + + @complexity Constant. + + @liveexample{The example compares several JSON types to the null pointer. + ,operator__notequal__nullptr_t} + + @since version 1.0.0 + */ + friend bool operator!=(const_reference v, std::nullptr_t) noexcept + { + return not v.is_null(); + } + + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, std::nullptr_t) + */ + friend bool operator!=(std::nullptr_t, const_reference v) noexcept + { + return not v.is_null(); + } + + /*! + @brief comparison: less than + + Compares whether one JSON value @a lhs is less than another JSON value @a + rhs according to the following rules: + - If @a lhs and @a rhs have the same type, the values are compared using + the default `<` operator. + - Integer and floating-point numbers are automatically converted before + comparison + - In case @a lhs and @a rhs have different types, the values are ignored + and the order of the types is considered, see + @ref operator<(const value_t, const value_t). + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than @a rhs + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__less} + + @since version 1.0.0 + */ + friend bool operator<(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + { + return *lhs.m_value.array < *rhs.m_value.array; + } + case value_t::object: + { + return *lhs.m_value.object < *rhs.m_value.object; + } + case value_t::null: + { + return false; + } + case value_t::string: + { + return *lhs.m_value.string < *rhs.m_value.string; + } + case value_t::boolean: + { + return lhs.m_value.boolean < rhs.m_value.boolean; + } + case value_t::number_integer: + { + return lhs.m_value.number_integer < rhs.m_value.number_integer; + } + case value_t::number_unsigned: + { + return lhs.m_value.number_unsigned < rhs.m_value.number_unsigned; + } + case value_t::number_float: + { + return lhs.m_value.number_float < rhs.m_value.number_float; + } + default: + { + return false; + } + } + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; + } + + // We only reach this line if we cannot compare values. In that case, + // we compare types. Note we have to call the operator explicitly, + // because MSVC has problems otherwise. + return operator<(lhs_type, rhs_type); + } + + /*! + @brief comparison: less than or equal + + Compares whether one JSON value @a lhs is less than or equal to another + JSON value by calculating `not (rhs < lhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than or equal to @a rhs + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greater} + + @since version 1.0.0 + */ + friend bool operator<=(const_reference lhs, const_reference rhs) noexcept + { + return not (rhs < lhs); + } + + /*! + @brief comparison: greater than + + Compares whether one JSON value @a lhs is greater than another + JSON value by calculating `not (lhs <= rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than to @a rhs + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__lessequal} + + @since version 1.0.0 + */ + friend bool operator>(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs <= rhs); + } + + /*! + @brief comparison: greater than or equal + + Compares whether one JSON value @a lhs is greater than or equal to another + JSON value by calculating `not (lhs < rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than or equal to @a rhs + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greaterequal} + + @since version 1.0.0 + */ + friend bool operator>=(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs < rhs); + } + + /// @} + + + /////////////////// + // serialization // + /////////////////// + + /// @name serialization + /// @{ + + /*! + @brief serialize to stream + + Serialize the given JSON value @a j to the output stream @a o. The JSON + value will be serialized using the @ref dump member function. The + indentation of the output can be controlled with the member variable + `width` of the output stream @a o. For instance, using the manipulator + `std::setw(4)` on @a o sets the indentation level to `4` and the + serialization result is the same as calling `dump(4)`. + + @note During serializaion, the locale and the precision of the output + stream @a o are changed. The original values are restored when the + function returns. + + @param[in,out] o stream to serialize to + @param[in] j JSON value to serialize + + @return the stream @a o + + @complexity Linear. + + @liveexample{The example below shows the serialization with different + parameters to `width` to adjust the indentation level.,operator_serialize} + + @since version 1.0.0 + */ + friend std::ostream& operator<<(std::ostream& o, const basic_json& j) + { + // read width member and use it as indentation parameter if nonzero + const bool pretty_print = (o.width() > 0); + const auto indentation = (pretty_print ? o.width() : 0); + + // reset width to 0 for subsequent calls to this stream + o.width(0); + + // fix locale problems + const auto old_locale = o.imbue(std::locale(std::locale(), new DecimalSeparator)); + // set precision + + // 6, 15 or 16 digits of precision allows round-trip IEEE 754 + // string->float->string, string->double->string or string->long + // double->string; to be safe, we read this value from + // std::numeric_limits::digits10 + const auto old_precision = o.precision(std::numeric_limits::digits10); + + // do the actual serialization + j.dump(o, pretty_print, static_cast(indentation)); + + // reset locale and precision + o.imbue(old_locale); + o.precision(old_precision); + return o; + } + + /*! + @brief serialize to stream + @copydoc operator<<(std::ostream&, const basic_json&) + */ + friend std::ostream& operator>>(const basic_json& j, std::ostream& o) + { + return o << j; + } + + /// @} + + + ///////////////////// + // deserialization // + ///////////////////// + + /// @name deserialization + /// @{ + + /*! + @brief deserialize from an array + + This function reads from an array of 1-byte values. + + @pre Each element of the container has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @param[in] array array to read from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function reading + from an array.,parse__array__parser_callback_t} + + @since version 2.0.3 + */ + template + static basic_json parse(T (&array)[N], + const parser_callback_t cb = nullptr) + { + // delegate the call to the iterator-range parse overload + return parse(std::begin(array), std::end(array), cb); + } + + /*! + @brief deserialize from string literal + + @tparam CharT character/literal type with size of 1 byte + @param[in] s string literal to read a serialized JSON value from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + @note String containers like `std::string` or @ref string_t can be parsed + with @ref parse(const ContiguousContainer&, const parser_callback_t) + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__string__parser_callback_t} + + @sa @ref parse(std::istream&, const parser_callback_t) for a version that + reads from an input stream + + @since version 1.0.0 (originally for @ref string_t) + */ + template::value and + std::is_integral::type>::value and + sizeof(typename std::remove_pointer::type) == 1, int>::type = 0> + static basic_json parse(const CharPT s, + const parser_callback_t cb = nullptr) + { + return parser(reinterpret_cast(s), cb).parse(); + } + + /*! + @brief deserialize from stream + + @param[in,out] i stream to read a serialized JSON value from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__istream__parser_callback_t} + + @sa @ref parse(const char*, const parser_callback_t) for a version + that reads from a string + + @since version 1.0.0 + */ + static basic_json parse(std::istream& i, + const parser_callback_t cb = nullptr) + { + return parser(i, cb).parse(); + } + + /*! + @copydoc parse(std::istream&, const parser_callback_t) + */ + static basic_json parse(std::istream&& i, + const parser_callback_t cb = nullptr) + { + return parser(i, cb).parse(); + } + + /*! + @brief deserialize from an iterator range with contiguous storage + + This function reads from an iterator range of a container with contiguous + storage of 1-byte values. Compatible container types include + `std::vector`, `std::string`, `std::array`, `std::valarray`, and + `std::initializer_list`. Furthermore, C-style arrays can be used with + `std::begin()`/`std::end()`. User-defined containers can be used as long + as they implement random-access iterators and a contiguous storage. + + @pre The iterator range is contiguous. Violating this precondition yields + undefined behavior. **This precondition is enforced with an assertion.** + @pre Each element in the range has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @warning There is no way to enforce all preconditions at compile-time. If + the function is called with noncompliant iterators and with + assertions switched off, the behavior is undefined and will most + likely yield segmentation violation. + + @tparam IteratorType iterator of container with contiguous storage + @param[in] first begin of the range to parse (included) + @param[in] last end of the range to parse (excluded) + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function reading + from an iterator range.,parse__iteratortype__parser_callback_t} + + @since version 2.0.3 + */ + template::iterator_category>::value, int>::type = 0> + static basic_json parse(IteratorType first, IteratorType last, + const parser_callback_t cb = nullptr) + { + // assertion to check that the iterator range is indeed contiguous, + // see http://stackoverflow.com/a/35008842/266378 for more discussion + assert(std::accumulate(first, last, std::make_pair(true, 0), + [&first](std::pair res, decltype(*first) val) + { + res.first &= (val == *(std::next(std::addressof(*first), res.second++))); + return res; + }).first); + + // assertion to check that each element is 1 byte long + static_assert(sizeof(typename std::iterator_traits::value_type) == 1, + "each element in the iterator range must have the size of 1 byte"); + + // if iterator range is empty, create a parser with an empty string + // to generate "unexpected EOF" error message + if (std::distance(first, last) <= 0) + { + return parser("").parse(); + } + + return parser(first, last, cb).parse(); + } + + /*! + @brief deserialize from a container with contiguous storage + + This function reads from a container with contiguous storage of 1-byte + values. Compatible container types include `std::vector`, `std::string`, + `std::array`, and `std::initializer_list`. User-defined containers can be + used as long as they implement random-access iterators and a contiguous + storage. + + @pre The container storage is contiguous. Violating this precondition + yields undefined behavior. **This precondition is enforced with an + assertion.** + @pre Each element of the container has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @warning There is no way to enforce all preconditions at compile-time. If + the function is called with a noncompliant container and with + assertions switched off, the behavior is undefined and will most + likely yield segmentation violation. + + @tparam ContiguousContainer container type with contiguous storage + @param[in] c container to read from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function reading + from a contiguous container.,parse__contiguouscontainer__parser_callback_t} + + @since version 2.0.3 + */ + template::value and + std::is_base_of< + std::random_access_iterator_tag, + typename std::iterator_traits()))>::iterator_category>::value + , int>::type = 0> + static basic_json parse(const ContiguousContainer& c, + const parser_callback_t cb = nullptr) + { + // delegate the call to the iterator-range parse overload + return parse(std::begin(c), std::end(c), cb); + } + + /*! + @brief deserialize from stream + + Deserializes an input stream to a JSON value. + + @param[in,out] i input stream to read a serialized JSON value from + @param[in,out] j JSON value to write the deserialized input to + + @throw std::invalid_argument in case of parse errors + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below shows how a JSON value is constructed by + reading a serialization from a stream.,operator_deserialize} + + @sa parse(std::istream&, const parser_callback_t) for a variant with a + parser callback function to filter values while parsing + + @since version 1.0.0 + */ + friend std::istream& operator<<(basic_json& j, std::istream& i) + { + j = parser(i).parse(); + return i; + } + + /*! + @brief deserialize from stream + @copydoc operator<<(basic_json&, std::istream&) + */ + friend std::istream& operator>>(std::istream& i, basic_json& j) + { + j = parser(i).parse(); + return i; + } + + /// @} + + + private: + /////////////////////////// + // convenience functions // + /////////////////////////// + + /*! + @brief return the type as string + + Returns the type name as string to be used in error messages - usually to + indicate that a function was called on a wrong JSON type. + + @return basically a string representation of a the @a m_type member + + @complexity Constant. + + @since version 1.0.0 + */ + std::string type_name() const + { + switch (m_type) + { + case value_t::null: + return "null"; + case value_t::object: + return "object"; + case value_t::array: + return "array"; + case value_t::string: + return "string"; + case value_t::boolean: + return "boolean"; + case value_t::discarded: + return "discarded"; + default: + return "number"; + } + } + + /*! + @brief calculates the extra space to escape a JSON string + + @param[in] s the string to escape + @return the number of characters required to escape string @a s + + @complexity Linear in the length of string @a s. + */ + static std::size_t extra_space(const string_t& s) noexcept + { + return std::accumulate(s.begin(), s.end(), size_t{}, + [](size_t res, typename string_t::value_type c) + { + switch (c) + { + case '"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + { + // from c (1 byte) to \x (2 bytes) + return res + 1; + } + + default: + { + if (c >= 0x00 and c <= 0x1f) + { + // from c (1 byte) to \uxxxx (6 bytes) + return res + 5; + } + else + { + return res; + } + } + } + }); + } + + /*! + @brief escape a string + + Escape a string by replacing certain special characters by a sequence of + an escape character (backslash) and another character and other control + characters by a sequence of "\u" followed by a four-digit hex + representation. + + @param[in] s the string to escape + @return the escaped string + + @complexity Linear in the length of string @a s. + */ + static string_t escape_string(const string_t& s) + { + const auto space = extra_space(s); + if (space == 0) + { + return s; + } + + // create a result string of necessary size + string_t result(s.size() + space, '\\'); + std::size_t pos = 0; + + for (const auto& c : s) + { + switch (c) + { + // quotation mark (0x22) + case '"': + { + result[pos + 1] = '"'; + pos += 2; + break; + } + + // reverse solidus (0x5c) + case '\\': + { + // nothing to change + pos += 2; + break; + } + + // backspace (0x08) + case '\b': + { + result[pos + 1] = 'b'; + pos += 2; + break; + } + + // formfeed (0x0c) + case '\f': + { + result[pos + 1] = 'f'; + pos += 2; + break; + } + + // newline (0x0a) + case '\n': + { + result[pos + 1] = 'n'; + pos += 2; + break; + } + + // carriage return (0x0d) + case '\r': + { + result[pos + 1] = 'r'; + pos += 2; + break; + } + + // horizontal tab (0x09) + case '\t': + { + result[pos + 1] = 't'; + pos += 2; + break; + } + + default: + { + if (c >= 0x00 and c <= 0x1f) + { + // convert a number 0..15 to its hex representation + // (0..f) + static const char hexify[16] = + { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + + // print character c as \uxxxx + for (const char m : + { 'u', '0', '0', hexify[c >> 4], hexify[c & 0x0f] + }) + { + result[++pos] = m; + } + + ++pos; + } + else + { + // all other characters are added as-is + result[pos++] = c; + } + break; + } + } + } + + return result; + } + + /*! + @brief internal implementation of the serialization function + + This function is called by the public member function dump and organizes + the serialization internally. The indentation level is propagated as + additional parameter. In case of arrays and objects, the function is + called recursively. Note that + + - strings and object keys are escaped using `escape_string()` + - integer numbers are converted implicitly via `operator<<` + - floating-point numbers are converted to a string using `"%g"` format + + @param[out] o stream to write to + @param[in] pretty_print whether the output shall be pretty-printed + @param[in] indent_step the indent level + @param[in] current_indent the current indent level (only used internally) + */ + void dump(std::ostream& o, + const bool pretty_print, + const unsigned int indent_step, + const unsigned int current_indent = 0) const + { + // variable to hold indentation for recursive calls + unsigned int new_indent = current_indent; + + switch (m_type) + { + case value_t::object: + { + if (m_value.object->empty()) + { + o << "{}"; + return; + } + + o << "{"; + + // increase indentation + if (pretty_print) + { + new_indent += indent_step; + o << "\n"; + } + + for (auto i = m_value.object->cbegin(); i != m_value.object->cend(); ++i) + { + if (i != m_value.object->cbegin()) + { + o << (pretty_print ? ",\n" : ","); + } + o << string_t(new_indent, ' ') << "\"" + << escape_string(i->first) << "\":" + << (pretty_print ? " " : ""); + i->second.dump(o, pretty_print, indent_step, new_indent); + } + + // decrease indentation + if (pretty_print) + { + new_indent -= indent_step; + o << "\n"; + } + + o << string_t(new_indent, ' ') + "}"; + return; + } + + case value_t::array: + { + if (m_value.array->empty()) + { + o << "[]"; + return; + } + + o << "["; + + // increase indentation + if (pretty_print) + { + new_indent += indent_step; + o << "\n"; + } + + for (auto i = m_value.array->cbegin(); i != m_value.array->cend(); ++i) + { + if (i != m_value.array->cbegin()) + { + o << (pretty_print ? ",\n" : ","); + } + o << string_t(new_indent, ' '); + i->dump(o, pretty_print, indent_step, new_indent); + } + + // decrease indentation + if (pretty_print) + { + new_indent -= indent_step; + o << "\n"; + } + + o << string_t(new_indent, ' ') << "]"; + return; + } + + case value_t::string: + { + o << string_t("\"") << escape_string(*m_value.string) << "\""; + return; + } + + case value_t::boolean: + { + o << (m_value.boolean ? "true" : "false"); + return; + } + + case value_t::number_integer: + { + o << m_value.number_integer; + return; + } + + case value_t::number_unsigned: + { + o << m_value.number_unsigned; + return; + } + + case value_t::number_float: + { + if (m_value.number_float == 0) + { + // special case for zero to get "0.0"/"-0.0" + o << (std::signbit(m_value.number_float) ? "-0.0" : "0.0"); + } + else + { + o << m_value.number_float; + } + return; + } + + case value_t::discarded: + { + o << ""; + return; + } + + case value_t::null: + { + o << "null"; + return; + } + } + } + + private: + ////////////////////// + // member variables // + ////////////////////// + + /// the type of the current element + value_t m_type = value_t::null; + + /// the value of the current element + json_value m_value = {}; + + + private: + /////////////// + // iterators // + /////////////// + + /*! + @brief an iterator for primitive JSON types + + This class models an iterator for primitive JSON types (boolean, number, + string). It's only purpose is to allow the iterator/const_iterator classes + to "iterate" over primitive values. Internally, the iterator is modeled by + a `difference_type` variable. Value begin_value (`0`) models the begin, + end_value (`1`) models past the end. + */ + class primitive_iterator_t + { + public: + /// set iterator to a defined beginning + void set_begin() noexcept + { + m_it = begin_value; + } + + /// set iterator to a defined past the end + void set_end() noexcept + { + m_it = end_value; + } + + /// return whether the iterator can be dereferenced + constexpr bool is_begin() const noexcept + { + return (m_it == begin_value); + } + + /// return whether the iterator is at end + constexpr bool is_end() const noexcept + { + return (m_it == end_value); + } + + /// return reference to the value to change and compare + operator difference_type& () noexcept + { + return m_it; + } + + /// return value to compare + constexpr operator difference_type () const noexcept + { + return m_it; + } + + private: + static constexpr difference_type begin_value = 0; + static constexpr difference_type end_value = begin_value + 1; + + /// iterator as signed integer type + difference_type m_it = std::numeric_limits::denorm_min(); + }; + + /*! + @brief an iterator value + + @note This structure could easily be a union, but MSVC currently does not + allow unions members with complex constructors, see + https://github.com/nlohmann/json/pull/105. + */ + struct internal_iterator + { + /// iterator for JSON objects + typename object_t::iterator object_iterator; + /// iterator for JSON arrays + typename array_t::iterator array_iterator; + /// generic iterator for all other types + primitive_iterator_t primitive_iterator; + + /// create an uninitialized internal_iterator + internal_iterator() noexcept + : object_iterator(), array_iterator(), primitive_iterator() + {} + }; + + /// proxy class for the iterator_wrapper functions + template + class iteration_proxy + { + private: + /// helper class for iteration + class iteration_proxy_internal + { + private: + /// the iterator + IteratorType anchor; + /// an index for arrays (used to create key names) + size_t array_index = 0; + + public: + explicit iteration_proxy_internal(IteratorType it) noexcept + : anchor(it) + {} + + /// dereference operator (needed for range-based for) + iteration_proxy_internal& operator*() + { + return *this; + } + + /// increment operator (needed for range-based for) + iteration_proxy_internal& operator++() + { + ++anchor; + ++array_index; + + return *this; + } + + /// inequality operator (needed for range-based for) + bool operator!= (const iteration_proxy_internal& o) const + { + return anchor != o.anchor; + } + + /// return key of the iterator + typename basic_json::string_t key() const + { + assert(anchor.m_object != nullptr); + + switch (anchor.m_object->type()) + { + // use integer array index as key + case value_t::array: + { + return std::to_string(array_index); + } + + // use key from the object + case value_t::object: + { + return anchor.key(); + } + + // use an empty key for all primitive types + default: + { + return ""; + } + } + } + + /// return value of the iterator + typename IteratorType::reference value() const + { + return anchor.value(); + } + }; + + /// the container to iterate + typename IteratorType::reference container; + + public: + /// construct iteration proxy from a container + explicit iteration_proxy(typename IteratorType::reference cont) + : container(cont) + {} + + /// return iterator begin (needed for range-based for) + iteration_proxy_internal begin() noexcept + { + return iteration_proxy_internal(container.begin()); + } + + /// return iterator end (needed for range-based for) + iteration_proxy_internal end() noexcept + { + return iteration_proxy_internal(container.end()); + } + }; + + public: + /*! + @brief a const random access iterator for the @ref basic_json class + + This class implements a const iterator for the @ref basic_json class. From + this class, the @ref iterator class is derived. + + @note An iterator is called *initialized* when a pointer to a JSON value + has been set (e.g., by a constructor or a copy assignment). If the + iterator is default-constructed, it is *uninitialized* and most + methods are undefined. **The library uses assertions to detect calls + on uninitialized iterators.** + + @requirement The class satisfies the following concept requirements: + - [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator): + The iterator that can be moved to point (forward and backward) to any + element in constant time. + + @since version 1.0.0 + */ + class const_iterator : public std::iterator + { + /// allow basic_json to access private members + friend class basic_json; + + public: + /// the type of the values when the iterator is dereferenced + using value_type = typename basic_json::value_type; + /// a type to represent differences between iterators + using difference_type = typename basic_json::difference_type; + /// defines a pointer to the type iterated over (value_type) + using pointer = typename basic_json::const_pointer; + /// defines a reference to the type iterated over (value_type) + using reference = typename basic_json::const_reference; + /// the category of the iterator + using iterator_category = std::bidirectional_iterator_tag; + + /// default constructor + const_iterator() = default; + + /*! + @brief constructor for a given JSON instance + @param[in] object pointer to a JSON object for this iterator + @pre object != nullptr + @post The iterator is initialized; i.e. `m_object != nullptr`. + */ + explicit const_iterator(pointer object) noexcept + : m_object(object) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + m_it.object_iterator = typename object_t::iterator(); + break; + } + + case basic_json::value_t::array: + { + m_it.array_iterator = typename array_t::iterator(); + break; + } + + default: + { + m_it.primitive_iterator = primitive_iterator_t(); + break; + } + } + } + + /*! + @brief copy constructor given a non-const iterator + @param[in] other iterator to copy from + @note It is not checked whether @a other is initialized. + */ + explicit const_iterator(const iterator& other) noexcept + : m_object(other.m_object) + { + if (m_object != nullptr) + { + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + m_it.object_iterator = other.m_it.object_iterator; + break; + } + + case basic_json::value_t::array: + { + m_it.array_iterator = other.m_it.array_iterator; + break; + } + + default: + { + m_it.primitive_iterator = other.m_it.primitive_iterator; + break; + } + } + } + } + + /*! + @brief copy constructor + @param[in] other iterator to copy from + @note It is not checked whether @a other is initialized. + */ + const_iterator(const const_iterator& other) noexcept + : m_object(other.m_object), m_it(other.m_it) + {} + + /*! + @brief copy assignment + @param[in,out] other iterator to copy from + @note It is not checked whether @a other is initialized. + */ + const_iterator& operator=(const_iterator other) noexcept( + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value + ) + { + std::swap(m_object, other.m_object); + std::swap(m_it, other.m_it); + return *this; + } + + private: + /*! + @brief set the iterator to the first value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_begin() noexcept + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + m_it.object_iterator = m_object->m_value.object->begin(); + break; + } + + case basic_json::value_t::array: + { + m_it.array_iterator = m_object->m_value.array->begin(); + break; + } + + case basic_json::value_t::null: + { + // set to end so begin()==end() is true: null is empty + m_it.primitive_iterator.set_end(); + break; + } + + default: + { + m_it.primitive_iterator.set_begin(); + break; + } + } + } + + /*! + @brief set the iterator past the last value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_end() noexcept + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + m_it.object_iterator = m_object->m_value.object->end(); + break; + } + + case basic_json::value_t::array: + { + m_it.array_iterator = m_object->m_value.array->end(); + break; + } + + default: + { + m_it.primitive_iterator.set_end(); + break; + } + } + } + + public: + /*! + @brief return a reference to the value pointed to by the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator*() const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return m_it.object_iterator->second; + } + + case basic_json::value_t::array: + { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return *m_it.array_iterator; + } + + case basic_json::value_t::null: + { + throw std::out_of_range("cannot get value"); + } + + default: + { + if (m_it.primitive_iterator.is_begin()) + { + return *m_object; + } + else + { + throw std::out_of_range("cannot get value"); + } + } + } + } + + /*! + @brief dereference the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + pointer operator->() const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return &(m_it.object_iterator->second); + } + + case basic_json::value_t::array: + { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return &*m_it.array_iterator; + } + + default: + { + if (m_it.primitive_iterator.is_begin()) + { + return m_object; + } + else + { + throw std::out_of_range("cannot get value"); + } + } + } + } + + /*! + @brief post-increment (it++) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + const_iterator operator++(int) + { + auto result = *this; + ++(*this); + return result; + } + + /*! + @brief pre-increment (++it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + const_iterator& operator++() + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + std::advance(m_it.object_iterator, 1); + break; + } + + case basic_json::value_t::array: + { + std::advance(m_it.array_iterator, 1); + break; + } + + default: + { + ++m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief post-decrement (it--) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + const_iterator operator--(int) + { + auto result = *this; + --(*this); + return result; + } + + /*! + @brief pre-decrement (--it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + const_iterator& operator--() + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + std::advance(m_it.object_iterator, -1); + break; + } + + case basic_json::value_t::array: + { + std::advance(m_it.array_iterator, -1); + break; + } + + default: + { + --m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief comparison: equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator==(const const_iterator& other) const + { + // if objects are not the same, the comparison is undefined + if (m_object != other.m_object) + { + throw std::domain_error("cannot compare iterators of different containers"); + } + + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + return (m_it.object_iterator == other.m_it.object_iterator); + } + + case basic_json::value_t::array: + { + return (m_it.array_iterator == other.m_it.array_iterator); + } + + default: + { + return (m_it.primitive_iterator == other.m_it.primitive_iterator); + } + } + } + + /*! + @brief comparison: not equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator!=(const const_iterator& other) const + { + return not operator==(other); + } + + /*! + @brief comparison: smaller + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<(const const_iterator& other) const + { + // if objects are not the same, the comparison is undefined + if (m_object != other.m_object) + { + throw std::domain_error("cannot compare iterators of different containers"); + } + + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + throw std::domain_error("cannot compare order of object iterators"); + } + + case basic_json::value_t::array: + { + return (m_it.array_iterator < other.m_it.array_iterator); + } + + default: + { + return (m_it.primitive_iterator < other.m_it.primitive_iterator); + } + } + } + + /*! + @brief comparison: less than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<=(const const_iterator& other) const + { + return not other.operator < (*this); + } + + /*! + @brief comparison: greater than + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>(const const_iterator& other) const + { + return not operator<=(other); + } + + /*! + @brief comparison: greater than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>=(const const_iterator& other) const + { + return not operator<(other); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + const_iterator& operator+=(difference_type i) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + throw std::domain_error("cannot use offsets with object iterators"); + } + + case basic_json::value_t::array: + { + std::advance(m_it.array_iterator, i); + break; + } + + default: + { + m_it.primitive_iterator += i; + break; + } + } + + return *this; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + const_iterator& operator-=(difference_type i) + { + return operator+=(-i); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + const_iterator operator+(difference_type i) + { + auto result = *this; + result += i; + return result; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + const_iterator operator-(difference_type i) + { + auto result = *this; + result -= i; + return result; + } + + /*! + @brief return difference + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + difference_type operator-(const const_iterator& other) const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + throw std::domain_error("cannot use offsets with object iterators"); + } + + case basic_json::value_t::array: + { + return m_it.array_iterator - other.m_it.array_iterator; + } + + default: + { + return m_it.primitive_iterator - other.m_it.primitive_iterator; + } + } + } + + /*! + @brief access to successor + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator[](difference_type n) const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + throw std::domain_error("cannot use operator[] for object iterators"); + } + + case basic_json::value_t::array: + { + return *std::next(m_it.array_iterator, n); + } + + case basic_json::value_t::null: + { + throw std::out_of_range("cannot get value"); + } + + default: + { + if (m_it.primitive_iterator == -n) + { + return *m_object; + } + else + { + throw std::out_of_range("cannot get value"); + } + } + } + } + + /*! + @brief return the key of an object iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + typename object_t::key_type key() const + { + assert(m_object != nullptr); + + if (m_object->is_object()) + { + return m_it.object_iterator->first; + } + else + { + throw std::domain_error("cannot use key() for non-object iterators"); + } + } + + /*! + @brief return the value of an iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference value() const + { + return operator*(); + } + + private: + /// associated JSON instance + pointer m_object = nullptr; + /// the actual iterator of the associated instance + internal_iterator m_it = internal_iterator(); + }; + + /*! + @brief a mutable random access iterator for the @ref basic_json class + + @requirement The class satisfies the following concept requirements: + - [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator): + The iterator that can be moved to point (forward and backward) to any + element in constant time. + - [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator): + It is possible to write to the pointed-to element. + + @since version 1.0.0 + */ + class iterator : public const_iterator + { + public: + using base_iterator = const_iterator; + using pointer = typename basic_json::pointer; + using reference = typename basic_json::reference; + + /// default constructor + iterator() = default; + + /// constructor for a given JSON instance + explicit iterator(pointer object) noexcept + : base_iterator(object) + {} + + /// copy constructor + iterator(const iterator& other) noexcept + : base_iterator(other) + {} + + /// copy assignment + iterator& operator=(iterator other) noexcept( + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value + ) + { + base_iterator::operator=(other); + return *this; + } + + /// return a reference to the value pointed to by the iterator + reference operator*() const + { + return const_cast(base_iterator::operator*()); + } + + /// dereference the iterator + pointer operator->() const + { + return const_cast(base_iterator::operator->()); + } + + /// post-increment (it++) + iterator operator++(int) + { + iterator result = *this; + base_iterator::operator++(); + return result; + } + + /// pre-increment (++it) + iterator& operator++() + { + base_iterator::operator++(); + return *this; + } + + /// post-decrement (it--) + iterator operator--(int) + { + iterator result = *this; + base_iterator::operator--(); + return result; + } + + /// pre-decrement (--it) + iterator& operator--() + { + base_iterator::operator--(); + return *this; + } + + /// add to iterator + iterator& operator+=(difference_type i) + { + base_iterator::operator+=(i); + return *this; + } + + /// subtract from iterator + iterator& operator-=(difference_type i) + { + base_iterator::operator-=(i); + return *this; + } + + /// add to iterator + iterator operator+(difference_type i) + { + auto result = *this; + result += i; + return result; + } + + /// subtract from iterator + iterator operator-(difference_type i) + { + auto result = *this; + result -= i; + return result; + } + + /// return difference + difference_type operator-(const iterator& other) const + { + return base_iterator::operator-(other); + } + + /// access to successor + reference operator[](difference_type n) const + { + return const_cast(base_iterator::operator[](n)); + } + + /// return the value of an iterator + reference value() const + { + return const_cast(base_iterator::value()); + } + }; + + /*! + @brief a template for a reverse iterator class + + @tparam Base the base iterator type to reverse. Valid types are @ref + iterator (to create @ref reverse_iterator) and @ref const_iterator (to + create @ref const_reverse_iterator). + + @requirement The class satisfies the following concept requirements: + - [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator): + The iterator that can be moved to point (forward and backward) to any + element in constant time. + - [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator): + It is possible to write to the pointed-to element (only if @a Base is + @ref iterator). + + @since version 1.0.0 + */ + template + class json_reverse_iterator : public std::reverse_iterator + { + public: + /// shortcut to the reverse iterator adaptor + using base_iterator = std::reverse_iterator; + /// the reference type for the pointed-to element + using reference = typename Base::reference; + + /// create reverse iterator from iterator + json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept + : base_iterator(it) + {} + + /// create reverse iterator from base class + json_reverse_iterator(const base_iterator& it) noexcept + : base_iterator(it) + {} + + /// post-increment (it++) + json_reverse_iterator operator++(int) + { + return base_iterator::operator++(1); + } + + /// pre-increment (++it) + json_reverse_iterator& operator++() + { + base_iterator::operator++(); + return *this; + } + + /// post-decrement (it--) + json_reverse_iterator operator--(int) + { + return base_iterator::operator--(1); + } + + /// pre-decrement (--it) + json_reverse_iterator& operator--() + { + base_iterator::operator--(); + return *this; + } + + /// add to iterator + json_reverse_iterator& operator+=(difference_type i) + { + base_iterator::operator+=(i); + return *this; + } + + /// add to iterator + json_reverse_iterator operator+(difference_type i) const + { + auto result = *this; + result += i; + return result; + } + + /// subtract from iterator + json_reverse_iterator operator-(difference_type i) const + { + auto result = *this; + result -= i; + return result; + } + + /// return difference + difference_type operator-(const json_reverse_iterator& other) const + { + return this->base() - other.base(); + } + + /// access to successor + reference operator[](difference_type n) const + { + return *(this->operator+(n)); + } + + /// return the key of an object iterator + typename object_t::key_type key() const + { + auto it = --this->base(); + return it.key(); + } + + /// return the value of an iterator + reference value() const + { + auto it = --this->base(); + return it.operator * (); + } + }; + + + private: + ////////////////////// + // lexer and parser // + ////////////////////// + + /*! + @brief lexical analysis + + This class organizes the lexical analysis during JSON deserialization. The + core of it is a scanner generated by [re2c](http://re2c.org) that + processes a buffer and recognizes tokens according to RFC 7159. + */ + class lexer + { + public: + /// token types for the parser + enum class token_type + { + uninitialized, ///< indicating the scanner is uninitialized + literal_true, ///< the `true` literal + literal_false, ///< the `false` literal + literal_null, ///< the `null` literal + value_string, ///< a string -- use get_string() for actual value + value_number, ///< a number -- use get_number() for actual value + begin_array, ///< the character for array begin `[` + begin_object, ///< the character for object begin `{` + end_array, ///< the character for array end `]` + end_object, ///< the character for object end `}` + name_separator, ///< the name separator `:` + value_separator, ///< the value separator `,` + parse_error, ///< indicating a parse error + end_of_input ///< indicating the end of the input buffer + }; + + /// the char type to use in the lexer + using lexer_char_t = unsigned char; + + /// a lexer from a buffer with given length + lexer(const lexer_char_t* buff, const size_t len) noexcept + : m_content(buff) + { + assert(m_content != nullptr); + m_start = m_cursor = m_content; + m_limit = m_content + len; + } + + /// a lexer from an input stream + explicit lexer(std::istream& s) + : m_stream(&s), m_line_buffer() + { + // fill buffer + fill_line_buffer(); + } + + // switch off unwanted functions (due to pointer members) + lexer() = delete; + lexer(const lexer&) = delete; + lexer operator=(const lexer&) = delete; + + /*! + @brief create a string from one or two Unicode code points + + There are two cases: (1) @a codepoint1 is in the Basic Multilingual + Plane (U+0000 through U+FFFF) and @a codepoint2 is 0, or (2) + @a codepoint1 and @a codepoint2 are a UTF-16 surrogate pair to + represent a code point above U+FFFF. + + @param[in] codepoint1 the code point (can be high surrogate) + @param[in] codepoint2 the code point (can be low surrogate or 0) + + @return string representation of the code point; the length of the + result string is between 1 and 4 characters. + + @throw std::out_of_range if code point is > 0x10ffff; example: `"code + points above 0x10FFFF are invalid"` + @throw std::invalid_argument if the low surrogate is invalid; example: + `""missing or wrong low surrogate""` + + @complexity Constant. + + @see + */ + static string_t to_unicode(const std::size_t codepoint1, + const std::size_t codepoint2 = 0) + { + // calculate the code point from the given code points + std::size_t codepoint = codepoint1; + + // check if codepoint1 is a high surrogate + if (codepoint1 >= 0xD800 and codepoint1 <= 0xDBFF) + { + // check if codepoint2 is a low surrogate + if (codepoint2 >= 0xDC00 and codepoint2 <= 0xDFFF) + { + codepoint = + // high surrogate occupies the most significant 22 bits + (codepoint1 << 10) + // low surrogate occupies the least significant 15 bits + + codepoint2 + // there is still the 0xD800, 0xDC00 and 0x10000 noise + // in the result so we have to subtract with: + // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 + - 0x35FDC00; + } + else + { + throw std::invalid_argument("missing or wrong low surrogate"); + } + } + + string_t result; + + if (codepoint < 0x80) + { + // 1-byte characters: 0xxxxxxx (ASCII) + result.append(1, static_cast(codepoint)); + } + else if (codepoint <= 0x7ff) + { + // 2-byte characters: 110xxxxx 10xxxxxx + result.append(1, static_cast(0xC0 | ((codepoint >> 6) & 0x1F))); + result.append(1, static_cast(0x80 | (codepoint & 0x3F))); + } + else if (codepoint <= 0xffff) + { + // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx + result.append(1, static_cast(0xE0 | ((codepoint >> 12) & 0x0F))); + result.append(1, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + result.append(1, static_cast(0x80 | (codepoint & 0x3F))); + } + else if (codepoint <= 0x10ffff) + { + // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + result.append(1, static_cast(0xF0 | ((codepoint >> 18) & 0x07))); + result.append(1, static_cast(0x80 | ((codepoint >> 12) & 0x3F))); + result.append(1, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + result.append(1, static_cast(0x80 | (codepoint & 0x3F))); + } + else + { + throw std::out_of_range("code points above 0x10FFFF are invalid"); + } + + return result; + } + + /// return name of values of type token_type (only used for errors) + static std::string token_type_name(const token_type t) + { + switch (t) + { + case token_type::uninitialized: + return ""; + case token_type::literal_true: + return "true literal"; + case token_type::literal_false: + return "false literal"; + case token_type::literal_null: + return "null literal"; + case token_type::value_string: + return "string literal"; + case token_type::value_number: + return "number literal"; + case token_type::begin_array: + return "'['"; + case token_type::begin_object: + return "'{'"; + case token_type::end_array: + return "']'"; + case token_type::end_object: + return "'}'"; + case token_type::name_separator: + return "':'"; + case token_type::value_separator: + return "','"; + case token_type::parse_error: + return ""; + case token_type::end_of_input: + return "end of input"; + default: + { + // catch non-enum values + return "unknown token"; // LCOV_EXCL_LINE + } + } + } + + /*! + This function implements a scanner for JSON. It is specified using + regular expressions that try to follow RFC 7159 as close as possible. + These regular expressions are then translated into a minimized + deterministic finite automaton (DFA) by the tool + [re2c](http://re2c.org). As a result, the translated code for this + function consists of a large block of code with `goto` jumps. + + @return the class of the next token read from the buffer + + @complexity Linear in the length of the input.\n + + Proposition: The loop below will always terminate for finite input.\n + + Proof (by contradiction): Assume a finite input. To loop forever, the + loop must never hit code with a `break` statement. The only code + snippets without a `break` statement are the continue statements for + whitespace and byte-order-marks. To loop forever, the input must be an + infinite sequence of whitespace or byte-order-marks. This contradicts + the assumption of finite input, q.e.d. + */ + token_type scan() + { + while (true) + { + // pointer for backtracking information + m_marker = nullptr; + + // remember the begin of the token + m_start = m_cursor; + assert(m_start != nullptr); + + + { + lexer_char_t yych; + unsigned int yyaccept = 0; + static const unsigned char yybm[] = + { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 32, 32, 0, 0, 32, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 160, 128, 0, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 0, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + }; + if ((m_limit - m_cursor) < 5) + { + fill_line_buffer(); + } + yych = *m_cursor; + if (yybm[0 + yych] & 32) + { + goto basic_json_parser_6; + } + if (yych <= '\\') + { + if (yych <= '-') + { + if (yych <= '"') + { + if (yych <= 0x00) + { + goto basic_json_parser_2; + } + if (yych <= '!') + { + goto basic_json_parser_4; + } + goto basic_json_parser_9; + } + else + { + if (yych <= '+') + { + goto basic_json_parser_4; + } + if (yych <= ',') + { + goto basic_json_parser_10; + } + goto basic_json_parser_12; + } + } + else + { + if (yych <= '9') + { + if (yych <= '/') + { + goto basic_json_parser_4; + } + if (yych <= '0') + { + goto basic_json_parser_13; + } + goto basic_json_parser_15; + } + else + { + if (yych <= ':') + { + goto basic_json_parser_17; + } + if (yych == '[') + { + goto basic_json_parser_19; + } + goto basic_json_parser_4; + } + } + } + else + { + if (yych <= 't') + { + if (yych <= 'f') + { + if (yych <= ']') + { + goto basic_json_parser_21; + } + if (yych <= 'e') + { + goto basic_json_parser_4; + } + goto basic_json_parser_23; + } + else + { + if (yych == 'n') + { + goto basic_json_parser_24; + } + if (yych <= 's') + { + goto basic_json_parser_4; + } + goto basic_json_parser_25; + } + } + else + { + if (yych <= '|') + { + if (yych == '{') + { + goto basic_json_parser_26; + } + goto basic_json_parser_4; + } + else + { + if (yych <= '}') + { + goto basic_json_parser_28; + } + if (yych == 0xEF) + { + goto basic_json_parser_30; + } + goto basic_json_parser_4; + } + } + } +basic_json_parser_2: + ++m_cursor; + { + last_token_type = token_type::end_of_input; + break; + } +basic_json_parser_4: + ++m_cursor; +basic_json_parser_5: + { + last_token_type = token_type::parse_error; + break; + } +basic_json_parser_6: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(); + } + yych = *m_cursor; + if (yybm[0 + yych] & 32) + { + goto basic_json_parser_6; + } + { + continue; + } +basic_json_parser_9: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych <= 0x1F) + { + goto basic_json_parser_5; + } + goto basic_json_parser_32; +basic_json_parser_10: + ++m_cursor; + { + last_token_type = token_type::value_separator; + break; + } +basic_json_parser_12: + yych = *++m_cursor; + if (yych <= '/') + { + goto basic_json_parser_5; + } + if (yych <= '0') + { + goto basic_json_parser_13; + } + if (yych <= '9') + { + goto basic_json_parser_15; + } + goto basic_json_parser_5; +basic_json_parser_13: + yyaccept = 1; + yych = *(m_marker = ++m_cursor); + if (yych <= 'D') + { + if (yych == '.') + { + goto basic_json_parser_37; + } + } + else + { + if (yych <= 'E') + { + goto basic_json_parser_38; + } + if (yych == 'e') + { + goto basic_json_parser_38; + } + } +basic_json_parser_14: + { + last_token_type = token_type::value_number; + break; + } +basic_json_parser_15: + yyaccept = 1; + m_marker = ++m_cursor; + if ((m_limit - m_cursor) < 3) + { + fill_line_buffer(); + } + yych = *m_cursor; + if (yybm[0 + yych] & 64) + { + goto basic_json_parser_15; + } + if (yych <= 'D') + { + if (yych == '.') + { + goto basic_json_parser_37; + } + goto basic_json_parser_14; + } + else + { + if (yych <= 'E') + { + goto basic_json_parser_38; + } + if (yych == 'e') + { + goto basic_json_parser_38; + } + goto basic_json_parser_14; + } +basic_json_parser_17: + ++m_cursor; + { + last_token_type = token_type::name_separator; + break; + } +basic_json_parser_19: + ++m_cursor; + { + last_token_type = token_type::begin_array; + break; + } +basic_json_parser_21: + ++m_cursor; + { + last_token_type = token_type::end_array; + break; + } +basic_json_parser_23: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 'a') + { + goto basic_json_parser_39; + } + goto basic_json_parser_5; +basic_json_parser_24: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 'u') + { + goto basic_json_parser_40; + } + goto basic_json_parser_5; +basic_json_parser_25: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 'r') + { + goto basic_json_parser_41; + } + goto basic_json_parser_5; +basic_json_parser_26: + ++m_cursor; + { + last_token_type = token_type::begin_object; + break; + } +basic_json_parser_28: + ++m_cursor; + { + last_token_type = token_type::end_object; + break; + } +basic_json_parser_30: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 0xBB) + { + goto basic_json_parser_42; + } + goto basic_json_parser_5; +basic_json_parser_31: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(); + } + yych = *m_cursor; +basic_json_parser_32: + if (yybm[0 + yych] & 128) + { + goto basic_json_parser_31; + } + if (yych <= 0x1F) + { + goto basic_json_parser_33; + } + if (yych <= '"') + { + goto basic_json_parser_34; + } + goto basic_json_parser_36; +basic_json_parser_33: + m_cursor = m_marker; + if (yyaccept == 0) + { + goto basic_json_parser_5; + } + else + { + goto basic_json_parser_14; + } +basic_json_parser_34: + ++m_cursor; + { + last_token_type = token_type::value_string; + break; + } +basic_json_parser_36: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(); + } + yych = *m_cursor; + if (yych <= 'e') + { + if (yych <= '/') + { + if (yych == '"') + { + goto basic_json_parser_31; + } + if (yych <= '.') + { + goto basic_json_parser_33; + } + goto basic_json_parser_31; + } + else + { + if (yych <= '\\') + { + if (yych <= '[') + { + goto basic_json_parser_33; + } + goto basic_json_parser_31; + } + else + { + if (yych == 'b') + { + goto basic_json_parser_31; + } + goto basic_json_parser_33; + } + } + } + else + { + if (yych <= 'q') + { + if (yych <= 'f') + { + goto basic_json_parser_31; + } + if (yych == 'n') + { + goto basic_json_parser_31; + } + goto basic_json_parser_33; + } + else + { + if (yych <= 's') + { + if (yych <= 'r') + { + goto basic_json_parser_31; + } + goto basic_json_parser_33; + } + else + { + if (yych <= 't') + { + goto basic_json_parser_31; + } + if (yych <= 'u') + { + goto basic_json_parser_43; + } + goto basic_json_parser_33; + } + } + } +basic_json_parser_37: + yych = *++m_cursor; + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych <= '9') + { + goto basic_json_parser_44; + } + goto basic_json_parser_33; +basic_json_parser_38: + yych = *++m_cursor; + if (yych <= ',') + { + if (yych == '+') + { + goto basic_json_parser_46; + } + goto basic_json_parser_33; + } + else + { + if (yych <= '-') + { + goto basic_json_parser_46; + } + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych <= '9') + { + goto basic_json_parser_47; + } + goto basic_json_parser_33; + } +basic_json_parser_39: + yych = *++m_cursor; + if (yych == 'l') + { + goto basic_json_parser_49; + } + goto basic_json_parser_33; +basic_json_parser_40: + yych = *++m_cursor; + if (yych == 'l') + { + goto basic_json_parser_50; + } + goto basic_json_parser_33; +basic_json_parser_41: + yych = *++m_cursor; + if (yych == 'u') + { + goto basic_json_parser_51; + } + goto basic_json_parser_33; +basic_json_parser_42: + yych = *++m_cursor; + if (yych == 0xBF) + { + goto basic_json_parser_52; + } + goto basic_json_parser_33; +basic_json_parser_43: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(); + } + yych = *m_cursor; + if (yych <= '@') + { + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych <= '9') + { + goto basic_json_parser_54; + } + goto basic_json_parser_33; + } + else + { + if (yych <= 'F') + { + goto basic_json_parser_54; + } + if (yych <= '`') + { + goto basic_json_parser_33; + } + if (yych <= 'f') + { + goto basic_json_parser_54; + } + goto basic_json_parser_33; + } +basic_json_parser_44: + yyaccept = 1; + m_marker = ++m_cursor; + if ((m_limit - m_cursor) < 3) + { + fill_line_buffer(); + } + yych = *m_cursor; + if (yych <= 'D') + { + if (yych <= '/') + { + goto basic_json_parser_14; + } + if (yych <= '9') + { + goto basic_json_parser_44; + } + goto basic_json_parser_14; + } + else + { + if (yych <= 'E') + { + goto basic_json_parser_38; + } + if (yych == 'e') + { + goto basic_json_parser_38; + } + goto basic_json_parser_14; + } +basic_json_parser_46: + yych = *++m_cursor; + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych >= ':') + { + goto basic_json_parser_33; + } +basic_json_parser_47: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(); + } + yych = *m_cursor; + if (yych <= '/') + { + goto basic_json_parser_14; + } + if (yych <= '9') + { + goto basic_json_parser_47; + } + goto basic_json_parser_14; +basic_json_parser_49: + yych = *++m_cursor; + if (yych == 's') + { + goto basic_json_parser_55; + } + goto basic_json_parser_33; +basic_json_parser_50: + yych = *++m_cursor; + if (yych == 'l') + { + goto basic_json_parser_56; + } + goto basic_json_parser_33; +basic_json_parser_51: + yych = *++m_cursor; + if (yych == 'e') + { + goto basic_json_parser_58; + } + goto basic_json_parser_33; +basic_json_parser_52: + ++m_cursor; + { + continue; + } +basic_json_parser_54: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(); + } + yych = *m_cursor; + if (yych <= '@') + { + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych <= '9') + { + goto basic_json_parser_60; + } + goto basic_json_parser_33; + } + else + { + if (yych <= 'F') + { + goto basic_json_parser_60; + } + if (yych <= '`') + { + goto basic_json_parser_33; + } + if (yych <= 'f') + { + goto basic_json_parser_60; + } + goto basic_json_parser_33; + } +basic_json_parser_55: + yych = *++m_cursor; + if (yych == 'e') + { + goto basic_json_parser_61; + } + goto basic_json_parser_33; +basic_json_parser_56: + ++m_cursor; + { + last_token_type = token_type::literal_null; + break; + } +basic_json_parser_58: + ++m_cursor; + { + last_token_type = token_type::literal_true; + break; + } +basic_json_parser_60: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(); + } + yych = *m_cursor; + if (yych <= '@') + { + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych <= '9') + { + goto basic_json_parser_63; + } + goto basic_json_parser_33; + } + else + { + if (yych <= 'F') + { + goto basic_json_parser_63; + } + if (yych <= '`') + { + goto basic_json_parser_33; + } + if (yych <= 'f') + { + goto basic_json_parser_63; + } + goto basic_json_parser_33; + } +basic_json_parser_61: + ++m_cursor; + { + last_token_type = token_type::literal_false; + break; + } +basic_json_parser_63: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(); + } + yych = *m_cursor; + if (yych <= '@') + { + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych <= '9') + { + goto basic_json_parser_31; + } + goto basic_json_parser_33; + } + else + { + if (yych <= 'F') + { + goto basic_json_parser_31; + } + if (yych <= '`') + { + goto basic_json_parser_33; + } + if (yych <= 'f') + { + goto basic_json_parser_31; + } + goto basic_json_parser_33; + } + } + + } + + return last_token_type; + } + + /*! + @brief append data from the stream to the line buffer + + This function is called by the scan() function when the end of the + buffer (`m_limit`) is reached and the `m_cursor` pointer cannot be + incremented without leaving the limits of the line buffer. Note re2c + decides when to call this function. + + If the lexer reads from contiguous storage, there is no trailing null + byte. Therefore, this function must make sure to add these padding + null bytes. + + If the lexer reads from an input stream, this function reads the next + line of the input. + + @pre + p p p p p p u u u u u x . . . . . . + ^ ^ ^ ^ + m_content m_start | m_limit + m_cursor + + @post + u u u u u x x x x x x x . . . . . . + ^ ^ ^ + | m_cursor m_limit + m_start + m_content + */ + void fill_line_buffer() + { + // number of processed characters (p) + const auto offset_start = m_start - m_content; + // offset for m_marker wrt. to m_start + const auto offset_marker = (m_marker == nullptr) ? 0 : m_marker - m_start; + // number of unprocessed characters (u) + const auto offset_cursor = m_cursor - m_start; + + // no stream is used or end of file is reached + if (m_stream == nullptr or m_stream->eof()) + { + // copy unprocessed characters to line buffer + m_line_buffer.clear(); + for (m_cursor = m_start; m_cursor != m_limit; ++m_cursor) + { + m_line_buffer.append(1, static_cast(*m_cursor)); + } + + // append 5 characters (size of longest keyword "false") to + // make sure that there is sufficient space between m_cursor + // and m_limit + m_line_buffer.append(5, '\0'); + } + else + { + // delete processed characters from line buffer + m_line_buffer.erase(0, static_cast(offset_start)); + // read next line from input stream + std::string line; + std::getline(*m_stream, line); + // add line with newline symbol to the line buffer + m_line_buffer += line + "\n"; + } + + // set pointers + m_content = reinterpret_cast(m_line_buffer.c_str()); + assert(m_content != nullptr); + m_start = m_content; + m_marker = m_start + offset_marker; + m_cursor = m_start + offset_cursor; + m_limit = m_start + m_line_buffer.size(); + } + + /// return string representation of last read token + string_t get_token_string() const + { + assert(m_start != nullptr); + return string_t(reinterpret_cast(m_start), + static_cast(m_cursor - m_start)); + } + + /*! + @brief return string value for string tokens + + The function iterates the characters between the opening and closing + quotes of the string value. The complete string is the range + [m_start,m_cursor). Consequently, we iterate from m_start+1 to + m_cursor-1. + + We differentiate two cases: + + 1. Escaped characters. In this case, a new character is constructed + according to the nature of the escape. Some escapes create new + characters (e.g., `"\\n"` is replaced by `"\n"`), some are copied + as is (e.g., `"\\\\"`). Furthermore, Unicode escapes of the shape + `"\\uxxxx"` need special care. In this case, to_unicode takes care + of the construction of the values. + 2. Unescaped characters are copied as is. + + @pre `m_cursor - m_start >= 2`, meaning the length of the last token + is at least 2 bytes which is trivially true for any string (which + consists of at least two quotes). + + " c1 c2 c3 ... " + ^ ^ + m_start m_cursor + + @complexity Linear in the length of the string.\n + + Lemma: The loop body will always terminate.\n + + Proof (by contradiction): Assume the loop body does not terminate. As + the loop body does not contain another loop, one of the called + functions must never return. The called functions are `std::strtoul` + and to_unicode. Neither function can loop forever, so the loop body + will never loop forever which contradicts the assumption that the loop + body does not terminate, q.e.d.\n + + Lemma: The loop condition for the for loop is eventually false.\n + + Proof (by contradiction): Assume the loop does not terminate. Due to + the above lemma, this can only be due to a tautological loop + condition; that is, the loop condition i < m_cursor - 1 must always be + true. Let x be the change of i for any loop iteration. Then + m_start + 1 + x < m_cursor - 1 must hold to loop indefinitely. This + can be rephrased to m_cursor - m_start - 2 > x. With the + precondition, we x <= 0, meaning that the loop condition holds + indefinitly if i is always decreased. However, observe that the value + of i is strictly increasing with each iteration, as it is incremented + by 1 in the iteration expression and never decremented inside the loop + body. Hence, the loop condition will eventually be false which + contradicts the assumption that the loop condition is a tautology, + q.e.d. + + @return string value of current token without opening and closing + quotes + @throw std::out_of_range if to_unicode fails + */ + string_t get_string() const + { + assert(m_cursor - m_start >= 2); + + string_t result; + result.reserve(static_cast(m_cursor - m_start - 2)); + + // iterate the result between the quotes + for (const lexer_char_t* i = m_start + 1; i < m_cursor - 1; ++i) + { + // process escaped characters + if (*i == '\\') + { + // read next character + ++i; + + switch (*i) + { + // the default escapes + case 't': + { + result += "\t"; + break; + } + case 'b': + { + result += "\b"; + break; + } + case 'f': + { + result += "\f"; + break; + } + case 'n': + { + result += "\n"; + break; + } + case 'r': + { + result += "\r"; + break; + } + case '\\': + { + result += "\\"; + break; + } + case '/': + { + result += "/"; + break; + } + case '"': + { + result += "\""; + break; + } + + // unicode + case 'u': + { + // get code xxxx from uxxxx + auto codepoint = std::strtoul(std::string(reinterpret_cast(i + 1), + 4).c_str(), nullptr, 16); + + // check if codepoint is a high surrogate + if (codepoint >= 0xD800 and codepoint <= 0xDBFF) + { + // make sure there is a subsequent unicode + if ((i + 6 >= m_limit) or * (i + 5) != '\\' or * (i + 6) != 'u') + { + throw std::invalid_argument("missing low surrogate"); + } + + // get code yyyy from uxxxx\uyyyy + auto codepoint2 = std::strtoul(std::string(reinterpret_cast + (i + 7), 4).c_str(), nullptr, 16); + result += to_unicode(codepoint, codepoint2); + // skip the next 10 characters (xxxx\uyyyy) + i += 10; + } + else + { + // add unicode character(s) + result += to_unicode(codepoint); + // skip the next four characters (xxxx) + i += 4; + } + break; + } + } + } + else + { + // all other characters are just copied to the end of the + // string + result.append(1, static_cast(*i)); + } + } + + return result; + } + + /*! + @brief parse floating point number + + This function (and its overloads) serves to select the most approprate + standard floating point number parsing function based on the type + supplied via the first parameter. Set this to @a + static_cast(nullptr). + + @param[in] type the @ref number_float_t in use + + @param[in,out] endptr recieves a pointer to the first character after + the number + + @return the floating point number + */ + long double str_to_float_t(long double* /* type */, char** endptr) const + { + return std::strtold(reinterpret_cast(m_start), endptr); + } + + /*! + @brief parse floating point number + + This function (and its overloads) serves to select the most approprate + standard floating point number parsing function based on the type + supplied via the first parameter. Set this to @a + static_cast(nullptr). + + @param[in] type the @ref number_float_t in use + + @param[in,out] endptr recieves a pointer to the first character after + the number + + @return the floating point number + */ + double str_to_float_t(double* /* type */, char** endptr) const + { + return std::strtod(reinterpret_cast(m_start), endptr); + } + + /*! + @brief parse floating point number + + This function (and its overloads) serves to select the most approprate + standard floating point number parsing function based on the type + supplied via the first parameter. Set this to @a + static_cast(nullptr). + + @param[in] type the @ref number_float_t in use + + @param[in,out] endptr recieves a pointer to the first character after + the number + + @return the floating point number + */ + float str_to_float_t(float* /* type */, char** endptr) const + { + return std::strtof(reinterpret_cast(m_start), endptr); + } + + /*! + @brief return number value for number tokens + + This function translates the last token into the most appropriate + number type (either integer, unsigned integer or floating point), + which is passed back to the caller via the result parameter. + + This function parses the integer component up to the radix point or + exponent while collecting information about the 'floating point + representation', which it stores in the result parameter. If there is + no radix point or exponent, and the number can fit into a @ref + number_integer_t or @ref number_unsigned_t then it sets the result + parameter accordingly. + + If the number is a floating point number the number is then parsed + using @a std:strtod (or @a std:strtof or @a std::strtold). + + @param[out] result @ref basic_json object to receive the number, or + NAN if the conversion read past the current token. The latter case + needs to be treated by the caller function. + */ + void get_number(basic_json& result) const + { + assert(m_start != nullptr); + + const lexer::lexer_char_t* curptr = m_start; + + // accumulate the integer conversion result (unsigned for now) + number_unsigned_t value = 0; + + // maximum absolute value of the relevant integer type + number_unsigned_t max; + + // temporarily store the type to avoid unecessary bitfield access + value_t type; + + // look for sign + if (*curptr == '-') + { + type = value_t::number_integer; + max = static_cast((std::numeric_limits::max)()) + 1; + curptr++; + } + else + { + type = value_t::number_unsigned; + max = static_cast((std::numeric_limits::max)()); + } + + // count the significant figures + for (; curptr < m_cursor; curptr++) + { + // quickly skip tests if a digit + if (*curptr < '0' || *curptr > '9') + { + if (*curptr == '.') + { + // don't count '.' but change to float + type = value_t::number_float; + continue; + } + // assume exponent (if not then will fail parse): change to + // float, stop counting and record exponent details + type = value_t::number_float; + break; + } + + // skip if definitely not an integer + if (type != value_t::number_float) + { + // multiply last value by ten and add the new digit + auto temp = value * 10 + *curptr - '0'; + + // test for overflow + if (temp < value || temp > max) + { + // overflow + type = value_t::number_float; + } + else + { + // no overflow - save it + value = temp; + } + } + } + + // save the value (if not a float) + if (type == value_t::number_unsigned) + { + result.m_value.number_unsigned = value; + } + else if (type == value_t::number_integer) + { + result.m_value.number_integer = -static_cast(value); + } + else + { + // parse with strtod + result.m_value.number_float = str_to_float_t(static_cast(nullptr), NULL); + } + + // save the type + result.m_type = type; + } + + private: + /// optional input stream + std::istream* m_stream = nullptr; + /// line buffer buffer for m_stream + string_t m_line_buffer {}; + /// the buffer pointer + const lexer_char_t* m_content = nullptr; + /// pointer to the beginning of the current symbol + const lexer_char_t* m_start = nullptr; + /// pointer for backtracking information + const lexer_char_t* m_marker = nullptr; + /// pointer to the current symbol + const lexer_char_t* m_cursor = nullptr; + /// pointer to the end of the buffer + const lexer_char_t* m_limit = nullptr; + /// the last token type + token_type last_token_type = token_type::end_of_input; + }; + + /*! + @brief syntax analysis + + This class implements a recursive decent parser. + */ + class parser + { + public: + /// a parser reading from a string literal + parser(const char* buff, const parser_callback_t cb = nullptr) + : callback(cb), + m_lexer(reinterpret_cast(buff), strlen(buff)) + {} + + /// a parser reading from an input stream + parser(std::istream& is, const parser_callback_t cb = nullptr) + : callback(cb), m_lexer(is) + {} + + /// a parser reading from an iterator range with contiguous storage + template::iterator_category, std::random_access_iterator_tag>::value + , int>::type + = 0> + parser(IteratorType first, IteratorType last, const parser_callback_t cb = nullptr) + : callback(cb), + m_lexer(reinterpret_cast(&(*first)), + static_cast(std::distance(first, last))) + {} + + /// public parser interface + basic_json parse() + { + // read first token + get_token(); + + basic_json result = parse_internal(true); + result.assert_invariant(); + + expect(lexer::token_type::end_of_input); + + // return parser result and replace it with null in case the + // top-level value was discarded by the callback function + return result.is_discarded() ? basic_json() : std::move(result); + } + + private: + /// the actual parser + basic_json parse_internal(bool keep) + { + auto result = basic_json(value_t::discarded); + + switch (last_token) + { + case lexer::token_type::begin_object: + { + if (keep and (not callback + or ((keep = callback(depth++, parse_event_t::object_start, result)) != 0))) + { + // explicitly set result to object to cope with {} + result.m_type = value_t::object; + result.m_value = value_t::object; + } + + // read next token + get_token(); + + // closing } -> we are done + if (last_token == lexer::token_type::end_object) + { + get_token(); + if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) + { + result = basic_json(value_t::discarded); + } + return result; + } + + // no comma is expected here + unexpect(lexer::token_type::value_separator); + + // otherwise: parse key-value pairs + do + { + // ugly, but could be fixed with loop reorganization + if (last_token == lexer::token_type::value_separator) + { + get_token(); + } + + // store key + expect(lexer::token_type::value_string); + const auto key = m_lexer.get_string(); + + bool keep_tag = false; + if (keep) + { + if (callback) + { + basic_json k(key); + keep_tag = callback(depth, parse_event_t::key, k); + } + else + { + keep_tag = true; + } + } + + // parse separator (:) + get_token(); + expect(lexer::token_type::name_separator); + + // parse and add value + get_token(); + auto value = parse_internal(keep); + if (keep and keep_tag and not value.is_discarded()) + { + result[key] = std::move(value); + } + } + while (last_token == lexer::token_type::value_separator); + + // closing } + expect(lexer::token_type::end_object); + get_token(); + if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) + { + result = basic_json(value_t::discarded); + } + + return result; + } + + case lexer::token_type::begin_array: + { + if (keep and (not callback + or ((keep = callback(depth++, parse_event_t::array_start, result)) != 0))) + { + // explicitly set result to object to cope with [] + result.m_type = value_t::array; + result.m_value = value_t::array; + } + + // read next token + get_token(); + + // closing ] -> we are done + if (last_token == lexer::token_type::end_array) + { + get_token(); + if (callback and not callback(--depth, parse_event_t::array_end, result)) + { + result = basic_json(value_t::discarded); + } + return result; + } + + // no comma is expected here + unexpect(lexer::token_type::value_separator); + + // otherwise: parse values + do + { + // ugly, but could be fixed with loop reorganization + if (last_token == lexer::token_type::value_separator) + { + get_token(); + } + + // parse value + auto value = parse_internal(keep); + if (keep and not value.is_discarded()) + { + result.push_back(std::move(value)); + } + } + while (last_token == lexer::token_type::value_separator); + + // closing ] + expect(lexer::token_type::end_array); + get_token(); + if (keep and callback and not callback(--depth, parse_event_t::array_end, result)) + { + result = basic_json(value_t::discarded); + } + + return result; + } + + case lexer::token_type::literal_null: + { + get_token(); + result.m_type = value_t::null; + break; + } + + case lexer::token_type::value_string: + { + const auto s = m_lexer.get_string(); + get_token(); + result = basic_json(s); + break; + } + + case lexer::token_type::literal_true: + { + get_token(); + result.m_type = value_t::boolean; + result.m_value = true; + break; + } + + case lexer::token_type::literal_false: + { + get_token(); + result.m_type = value_t::boolean; + result.m_value = false; + break; + } + + case lexer::token_type::value_number: + { + m_lexer.get_number(result); + get_token(); + break; + } + + default: + { + // the last token was unexpected + unexpect(last_token); + } + } + + if (keep and callback and not callback(depth, parse_event_t::value, result)) + { + result = basic_json(value_t::discarded); + } + return result; + } + + /// get next token from lexer + typename lexer::token_type get_token() + { + last_token = m_lexer.scan(); + return last_token; + } + + void expect(typename lexer::token_type t) const + { + if (t != last_token) + { + std::string error_msg = "parse error - unexpected "; + error_msg += (last_token == lexer::token_type::parse_error ? ("'" + m_lexer.get_token_string() + + "'") : + lexer::token_type_name(last_token)); + error_msg += "; expected " + lexer::token_type_name(t); + throw std::invalid_argument(error_msg); + } + } + + void unexpect(typename lexer::token_type t) const + { + if (t == last_token) + { + std::string error_msg = "parse error - unexpected "; + error_msg += (last_token == lexer::token_type::parse_error ? ("'" + m_lexer.get_token_string() + + "'") : + lexer::token_type_name(last_token)); + throw std::invalid_argument(error_msg); + } + } + + private: + /// current level of recursion + int depth = 0; + /// callback function + const parser_callback_t callback = nullptr; + /// the type of the last read token + typename lexer::token_type last_token = lexer::token_type::uninitialized; + /// the lexer + lexer m_lexer; + }; + + public: + /*! + @brief JSON Pointer + + A JSON pointer defines a string syntax for identifying a specific value + within a JSON document. It can be used with functions `at` and + `operator[]`. Furthermore, JSON pointers are the base for JSON patches. + + @sa [RFC 6901](https://tools.ietf.org/html/rfc6901) + + @since version 2.0.0 + */ + class json_pointer + { + /// allow basic_json to access private members + friend class basic_json; + + public: + /*! + @brief create JSON pointer + + Create a JSON pointer according to the syntax described in + [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). + + @param[in] s string representing the JSON pointer; if omitted, the + empty string is assumed which references the whole JSON + value + + @throw std::domain_error if reference token is nonempty and does not + begin with a slash (`/`); example: `"JSON pointer must be empty or + begin with /"` + @throw std::domain_error if a tilde (`~`) is not followed by `0` + (representing `~`) or `1` (representing `/`); example: `"escape error: + ~ must be followed with 0 or 1"` + + @liveexample{The example shows the construction several valid JSON + pointers as well as the exceptional behavior.,json_pointer} + + @since version 2.0.0 + */ + explicit json_pointer(const std::string& s = "") + : reference_tokens(split(s)) + {} + + /*! + @brief return a string representation of the JSON pointer + + @invariant For each JSON pointer `ptr`, it holds: + @code {.cpp} + ptr == json_pointer(ptr.to_string()); + @endcode + + @return a string representation of the JSON pointer + + @liveexample{The example shows the result of `to_string`., + json_pointer__to_string} + + @since version 2.0.0 + */ + std::string to_string() const noexcept + { + return std::accumulate(reference_tokens.begin(), + reference_tokens.end(), std::string{}, + [](const std::string & a, const std::string & b) + { + return a + "/" + escape(b); + }); + } + + /// @copydoc to_string() + operator std::string() const + { + return to_string(); + } + + private: + /// remove and return last reference pointer + std::string pop_back() + { + if (is_root()) + { + throw std::domain_error("JSON pointer has no parent"); + } + + auto last = reference_tokens.back(); + reference_tokens.pop_back(); + return last; + } + + /// return whether pointer points to the root document + bool is_root() const + { + return reference_tokens.empty(); + } + + json_pointer top() const + { + if (is_root()) + { + throw std::domain_error("JSON pointer has no parent"); + } + + json_pointer result = *this; + result.reference_tokens = {reference_tokens[0]}; + return result; + } + + /*! + @brief create and return a reference to the pointed to value + + @complexity Linear in the number of reference tokens. + */ + reference get_and_create(reference j) const + { + pointer result = &j; + + // in case no reference tokens exist, return a reference to the + // JSON value j which will be overwritten by a primitive value + for (const auto& reference_token : reference_tokens) + { + switch (result->m_type) + { + case value_t::null: + { + if (reference_token == "0") + { + // start a new array if reference token is 0 + result = &result->operator[](0); + } + else + { + // start a new object otherwise + result = &result->operator[](reference_token); + } + break; + } + + case value_t::object: + { + // create an entry in the object + result = &result->operator[](reference_token); + break; + } + + case value_t::array: + { + // create an entry in the array + result = &result->operator[](static_cast(std::stoi(reference_token))); + break; + } + + /* + The following code is only reached if there exists a + reference token _and_ the current value is primitive. In + this case, we have an error situation, because primitive + values may only occur as single value; that is, with an + empty list of reference tokens. + */ + default: + { + throw std::domain_error("invalid value to unflatten"); + } + } + } + + return *result; + } + + /*! + @brief return a reference to the pointed to value + + @note This version does not throw if a value is not present, but tries + to create nested values instead. For instance, calling this function + with pointer `"/this/that"` on a null value is equivalent to calling + `operator[]("this").operator[]("that")` on that value, effectively + changing the null value to an object. + + @param[in] ptr a JSON value + + @return reference to the JSON value pointed to by the JSON pointer + + @complexity Linear in the length of the JSON pointer. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + */ + reference get_unchecked(pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + // convert null values to arrays or objects before continuing + if (ptr->m_type == value_t::null) + { + // check if reference token is a number + const bool nums = std::all_of(reference_token.begin(), + reference_token.end(), + [](const char x) + { + return std::isdigit(x); + }); + + // change value to array for numbers or "-" or to object + // otherwise + if (nums or reference_token == "-") + { + *ptr = value_t::array; + } + else + { + *ptr = value_t::object; + } + } + + switch (ptr->m_type) + { + case value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case value_t::array: + { + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + if (reference_token == "-") + { + // explicityly treat "-" as index beyond the end + ptr = &ptr->operator[](ptr->m_value.array->size()); + } + else + { + // convert array index to number; unchecked access + ptr = &ptr->operator[](static_cast(std::stoi(reference_token))); + } + break; + } + + default: + { + throw std::out_of_range("unresolved reference token '" + reference_token + "'"); + } + } + } + + return *ptr; + } + + reference get_checked(pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case value_t::array: + { + if (reference_token == "-") + { + // "-" always fails the range check + throw std::out_of_range("array index '-' (" + + std::to_string(ptr->m_value.array->size()) + + ") is out of range"); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + // note: at performs range check + ptr = &ptr->at(static_cast(std::stoi(reference_token))); + break; + } + + default: + { + throw std::out_of_range("unresolved reference token '" + reference_token + "'"); + } + } + } + + return *ptr; + } + + /*! + @brief return a const reference to the pointed to value + + @param[in] ptr a JSON value + + @return const reference to the JSON value pointed to by the JSON + pointer + */ + const_reference get_unchecked(const_pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case value_t::array: + { + if (reference_token == "-") + { + // "-" cannot be used for const access + throw std::out_of_range("array index '-' (" + + std::to_string(ptr->m_value.array->size()) + + ") is out of range"); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + // use unchecked array access + ptr = &ptr->operator[](static_cast(std::stoi(reference_token))); + break; + } + + default: + { + throw std::out_of_range("unresolved reference token '" + reference_token + "'"); + } + } + } + + return *ptr; + } + + const_reference get_checked(const_pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case value_t::array: + { + if (reference_token == "-") + { + // "-" always fails the range check + throw std::out_of_range("array index '-' (" + + std::to_string(ptr->m_value.array->size()) + + ") is out of range"); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + // note: at performs range check + ptr = &ptr->at(static_cast(std::stoi(reference_token))); + break; + } + + default: + { + throw std::out_of_range("unresolved reference token '" + reference_token + "'"); + } + } + } + + return *ptr; + } + + /// split the string input to reference tokens + static std::vector split(const std::string& reference_string) + { + std::vector result; + + // special case: empty reference string -> no reference tokens + if (reference_string.empty()) + { + return result; + } + + // check if nonempty reference string begins with slash + if (reference_string[0] != '/') + { + throw std::domain_error("JSON pointer must be empty or begin with '/'"); + } + + // extract the reference tokens: + // - slash: position of the last read slash (or end of string) + // - start: position after the previous slash + for ( + // search for the first slash after the first character + size_t slash = reference_string.find_first_of("/", 1), + // set the beginning of the first reference token + start = 1; + // we can stop if start == string::npos+1 = 0 + start != 0; + // set the beginning of the next reference token + // (will eventually be 0 if slash == std::string::npos) + start = slash + 1, + // find next slash + slash = reference_string.find_first_of("/", start)) + { + // use the text between the beginning of the reference token + // (start) and the last slash (slash). + auto reference_token = reference_string.substr(start, slash - start); + + // check reference tokens are properly escaped + for (size_t pos = reference_token.find_first_of("~"); + pos != std::string::npos; + pos = reference_token.find_first_of("~", pos + 1)) + { + assert(reference_token[pos] == '~'); + + // ~ must be followed by 0 or 1 + if (pos == reference_token.size() - 1 or + (reference_token[pos + 1] != '0' and + reference_token[pos + 1] != '1')) + { + throw std::domain_error("escape error: '~' must be followed with '0' or '1'"); + } + } + + // finally, store the reference token + unescape(reference_token); + result.push_back(reference_token); + } + + return result; + } + + private: + /*! + @brief replace all occurrences of a substring by another string + + @param[in,out] s the string to manipulate + @param[in] f the substring to replace with @a t + @param[in] t the string to replace @a f + + @return The string @a s where all occurrences of @a f are replaced + with @a t. + + @pre The search string @a f must not be empty. + + @since version 2.0.0 + */ + static void replace_substring(std::string& s, + const std::string& f, + const std::string& t) + { + assert(not f.empty()); + + for ( + size_t pos = s.find(f); // find first occurrence of f + pos != std::string::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t + pos = s.find(f, pos + t.size()) // find next occurrence of f + ); + } + + /// escape tilde and slash + static std::string escape(std::string s) + { + // escape "~"" to "~0" and "/" to "~1" + replace_substring(s, "~", "~0"); + replace_substring(s, "/", "~1"); + return s; + } + + /// unescape tilde and slash + static void unescape(std::string& s) + { + // first transform any occurrence of the sequence '~1' to '/' + replace_substring(s, "~1", "/"); + // then transform any occurrence of the sequence '~0' to '~' + replace_substring(s, "~0", "~"); + } + + /*! + @param[in] reference_string the reference string to the current value + @param[in] value the value to consider + @param[in,out] result the result object to insert values to + + @note Empty objects or arrays are flattened to `null`. + */ + static void flatten(const std::string& reference_string, + const basic_json& value, + basic_json& result) + { + switch (value.m_type) + { + case value_t::array: + { + if (value.m_value.array->empty()) + { + // flatten empty array as null + result[reference_string] = nullptr; + } + else + { + // iterate array and use index as reference string + for (size_t i = 0; i < value.m_value.array->size(); ++i) + { + flatten(reference_string + "/" + std::to_string(i), + value.m_value.array->operator[](i), result); + } + } + break; + } + + case value_t::object: + { + if (value.m_value.object->empty()) + { + // flatten empty object as null + result[reference_string] = nullptr; + } + else + { + // iterate object and use keys as reference string + for (const auto& element : *value.m_value.object) + { + flatten(reference_string + "/" + escape(element.first), + element.second, result); + } + } + break; + } + + default: + { + // add primitive value with its reference string + result[reference_string] = value; + break; + } + } + } + + /*! + @param[in] value flattened JSON + + @return unflattened JSON + */ + static basic_json unflatten(const basic_json& value) + { + if (not value.is_object()) + { + throw std::domain_error("only objects can be unflattened"); + } + + basic_json result; + + // iterate the JSON object values + for (const auto& element : *value.m_value.object) + { + if (not element.second.is_primitive()) + { + throw std::domain_error("values in object must be primitive"); + } + + // assign value to reference pointed to by JSON pointer; Note + // that if the JSON pointer is "" (i.e., points to the whole + // value), function get_and_create returns a reference to + // result itself. An assignment will then create a primitive + // value. + json_pointer(element.first).get_and_create(result) = element.second; + } + + return result; + } + + private: + /// the reference tokens + std::vector reference_tokens {}; + }; + + ////////////////////////// + // JSON Pointer support // + ////////////////////////// + + /// @name JSON Pointer functions + /// @{ + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. Similar to @ref operator[](const typename + object_t::key_type&), `null` values are created in arrays and objects if + necessary. + + In particular: + - If the JSON pointer points to an object key that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. + - If the JSON pointer points to an array index that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. All indices between the current maximum and the given + index are also filled with `null`. + - The special value `-` is treated as a synonym for the index past the + end. + + @param[in] ptr a JSON pointer + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,operatorjson_pointer} + + @since version 2.0.0 + */ + reference operator[](const json_pointer& ptr) + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. The function does not change the JSON + value; no `null` values are created. In particular, the the special value + `-` yields an exception. + + @param[in] ptr JSON pointer to the desired element + + @return const reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,operatorjson_pointer_const} + + @since version 2.0.0 + */ + const_reference operator[](const json_pointer& ptr) const + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a reference to the element at with specified JSON pointer @a ptr, + with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,at_json_pointer} + + @since version 2.0.0 + */ + reference at(const json_pointer& ptr) + { + return ptr.get_checked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a const reference to the element at with specified JSON pointer @a + ptr, with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,at_json_pointer_const} + + @since version 2.0.0 + */ + const_reference at(const json_pointer& ptr) const + { + return ptr.get_checked(this); + } + + /*! + @brief return flattened JSON value + + The function creates a JSON object whose keys are JSON pointers (see [RFC + 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all + primitive. The original JSON value can be restored using the @ref + unflatten() function. + + @return an object that maps JSON pointers to primitve values + + @note Empty objects and arrays are flattened to `null` and will not be + reconstructed correctly by the @ref unflatten() function. + + @complexity Linear in the size the JSON value. + + @liveexample{The following code shows how a JSON object is flattened to an + object whose keys consist of JSON pointers.,flatten} + + @sa @ref unflatten() for the reverse function + + @since version 2.0.0 + */ + basic_json flatten() const + { + basic_json result(value_t::object); + json_pointer::flatten("", *this, result); + return result; + } + + /*! + @brief unflatten a previously flattened JSON value + + The function restores the arbitrary nesting of a JSON value that has been + flattened before using the @ref flatten() function. The JSON value must + meet certain constraints: + 1. The value must be an object. + 2. The keys must be JSON pointers (see + [RFC 6901](https://tools.ietf.org/html/rfc6901)) + 3. The mapped values must be primitive JSON types. + + @return the original JSON from a flattened version + + @note Empty objects and arrays are flattened by @ref flatten() to `null` + values and can not unflattened to their original type. Apart from + this example, for a JSON value `j`, the following is always true: + `j == j.flatten().unflatten()`. + + @complexity Linear in the size the JSON value. + + @liveexample{The following code shows how a flattened JSON object is + unflattened into the original nested JSON object.,unflatten} + + @sa @ref flatten() for the reverse function + + @since version 2.0.0 + */ + basic_json unflatten() const + { + return json_pointer::unflatten(*this); + } + + /// @} + + ////////////////////////// + // JSON Patch functions // + ////////////////////////// + + /// @name JSON Patch functions + /// @{ + + /*! + @brief applies a JSON patch + + [JSON Patch](http://jsonpatch.com) defines a JSON document structure for + expressing a sequence of operations to apply to a JSON) document. With + this funcion, a JSON Patch is applied to the current JSON value by + executing all operations from the patch. + + @param[in] json_patch JSON patch document + @return patched document + + @note The application of a patch is atomic: Either all operations succeed + and the patched document is returned or an exception is thrown. In + any case, the original value is not changed: the patch is applied + to a copy of the value. + + @throw std::out_of_range if a JSON pointer inside the patch could not + be resolved successfully in the current JSON value; example: `"key baz + not found"` + @throw invalid_argument if the JSON patch is malformed (e.g., mandatory + attributes are missing); example: `"operation add must have member path"` + + @complexity Linear in the size of the JSON value and the length of the + JSON patch. As usually only a fraction of the JSON value is affected by + the patch, the complexity can usually be neglected. + + @liveexample{The following code shows how a JSON patch is applied to a + value.,patch} + + @sa @ref diff -- create a JSON patch by comparing two JSON values + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) + + @since version 2.0.0 + */ + basic_json patch(const basic_json& json_patch) const + { + // make a working copy to apply the patch to + basic_json result = *this; + + // the valid JSON Patch operations + enum class patch_operations {add, remove, replace, move, copy, test, invalid}; + + const auto get_op = [](const std::string op) + { + if (op == "add") + { + return patch_operations::add; + } + if (op == "remove") + { + return patch_operations::remove; + } + if (op == "replace") + { + return patch_operations::replace; + } + if (op == "move") + { + return patch_operations::move; + } + if (op == "copy") + { + return patch_operations::copy; + } + if (op == "test") + { + return patch_operations::test; + } + + return patch_operations::invalid; + }; + + // wrapper for "add" operation; add value at ptr + const auto operation_add = [&result](json_pointer & ptr, basic_json val) + { + // adding to the root of the target document means replacing it + if (ptr.is_root()) + { + result = val; + } + else + { + // make sure the top element of the pointer exists + json_pointer top_pointer = ptr.top(); + if (top_pointer != ptr) + { + result.at(top_pointer); + } + + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + basic_json& parent = result[ptr]; + + switch (parent.m_type) + { + case value_t::null: + case value_t::object: + { + // use operator[] to add value + parent[last_path] = val; + break; + } + + case value_t::array: + { + if (last_path == "-") + { + // special case: append to back + parent.push_back(val); + } + else + { + const auto idx = std::stoi(last_path); + if (static_cast(idx) > parent.size()) + { + // avoid undefined behavior + throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); + } + else + { + // default case: insert add offset + parent.insert(parent.begin() + static_cast(idx), val); + } + } + break; + } + + default: + { + // if there exists a parent it cannot be primitive + assert(false); // LCOV_EXCL_LINE + } + } + } + }; + + // wrapper for "remove" operation; remove value at ptr + const auto operation_remove = [&result](json_pointer & ptr) + { + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + basic_json& parent = result.at(ptr); + + // remove child + if (parent.is_object()) + { + // perform range check + auto it = parent.find(last_path); + if (it != parent.end()) + { + parent.erase(it); + } + else + { + throw std::out_of_range("key '" + last_path + "' not found"); + } + } + else if (parent.is_array()) + { + // note erase performs range check + parent.erase(static_cast(std::stoi(last_path))); + } + }; + + // type check + if (not json_patch.is_array()) + { + // a JSON patch must be an array of objects + throw std::invalid_argument("JSON patch must be an array of objects"); + } + + // iterate and apply th eoperations + for (const auto& val : json_patch) + { + // wrapper to get a value for an operation + const auto get_value = [&val](const std::string & op, + const std::string & member, + bool string_type) -> basic_json& + { + // find value + auto it = val.m_value.object->find(member); + + // context-sensitive error message + const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'"; + + // check if desired value is present + if (it == val.m_value.object->end()) + { + throw std::invalid_argument(error_msg + " must have member '" + member + "'"); + } + + // check if result is of type string + if (string_type and not it->second.is_string()) + { + throw std::invalid_argument(error_msg + " must have string member '" + member + "'"); + } + + // no error: return value + return it->second; + }; + + // type check + if (not val.is_object()) + { + throw std::invalid_argument("JSON patch must be an array of objects"); + } + + // collect mandatory members + const std::string op = get_value("op", "op", true); + const std::string path = get_value(op, "path", true); + json_pointer ptr(path); + + switch (get_op(op)) + { + case patch_operations::add: + { + operation_add(ptr, get_value("add", "value", false)); + break; + } + + case patch_operations::remove: + { + operation_remove(ptr); + break; + } + + case patch_operations::replace: + { + // the "path" location must exist - use at() + result.at(ptr) = get_value("replace", "value", false); + break; + } + + case patch_operations::move: + { + const std::string from_path = get_value("move", "from", true); + json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json v = result.at(from_ptr); + + // The move operation is functionally identical to a + // "remove" operation on the "from" location, followed + // immediately by an "add" operation at the target + // location with the value that was just removed. + operation_remove(from_ptr); + operation_add(ptr, v); + break; + } + + case patch_operations::copy: + { + const std::string from_path = get_value("copy", "from", true);; + const json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + result[ptr] = result.at(from_ptr); + break; + } + + case patch_operations::test: + { + bool success = false; + try + { + // check if "value" matches the one at "path" + // the "path" location must exist - use at() + success = (result.at(ptr) == get_value("test", "value", false)); + } + catch (std::out_of_range&) + { + // ignore out of range errors: success remains false + } + + // throw an exception if test fails + if (not success) + { + throw std::domain_error("unsuccessful: " + val.dump()); + } + + break; + } + + case patch_operations::invalid: + { + // op must be "add", "remove", "replace", "move", "copy", or + // "test" + throw std::invalid_argument("operation value '" + op + "' is invalid"); + } + } + } + + return result; + } + + /*! + @brief creates a diff as a JSON patch + + Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can + be changed into the value @a target by calling @ref patch function. + + @invariant For two JSON values @a source and @a target, the following code + yields always `true`: + @code {.cpp} + source.patch(diff(source, target)) == target; + @endcode + + @note Currently, only `remove`, `add`, and `replace` operations are + generated. + + @param[in] source JSON value to copare from + @param[in] target JSON value to copare against + @param[in] path helper value to create JSON pointers + + @return a JSON patch to convert the @a source to @a target + + @complexity Linear in the lengths of @a source and @a target. + + @liveexample{The following code shows how a JSON patch is created as a + diff for two JSON values.,diff} + + @sa @ref patch -- apply a JSON patch + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + + @since version 2.0.0 + */ + static basic_json diff(const basic_json& source, + const basic_json& target, + const std::string& path = "") + { + // the patch + basic_json result(value_t::array); + + // if the values are the same, return empty patch + if (source == target) + { + return result; + } + + if (source.type() != target.type()) + { + // different types: replace value + result.push_back( + { + {"op", "replace"}, + {"path", path}, + {"value", target} + }); + } + else + { + switch (source.type()) + { + case value_t::array: + { + // first pass: traverse common elements + size_t i = 0; + while (i < source.size() and i < target.size()) + { + // recursive call to compare array values at index i + auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i)); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + ++i; + } + + // i now reached the end of at least one array + // in a second pass, traverse the remaining elements + + // remove my remaining elements + const auto end_index = static_cast(result.size()); + while (i < source.size()) + { + // add operations in reverse order to avoid invalid + // indices + result.insert(result.begin() + end_index, object( + { + {"op", "remove"}, + {"path", path + "/" + std::to_string(i)} + })); + ++i; + } + + // add other remaining elements + while (i < target.size()) + { + result.push_back( + { + {"op", "add"}, + {"path", path + "/" + std::to_string(i)}, + {"value", target[i]} + }); + ++i; + } + + break; + } + + case value_t::object: + { + // first pass: traverse this object's elements + for (auto it = source.begin(); it != source.end(); ++it) + { + // escape the key name to be used in a JSON patch + const auto key = json_pointer::escape(it.key()); + + if (target.find(it.key()) != target.end()) + { + // recursive call to compare object values at key it + auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + } + else + { + // found a key that is not in o -> remove it + result.push_back(object( + { + {"op", "remove"}, + {"path", path + "/" + key} + })); + } + } + + // second pass: traverse other object's elements + for (auto it = target.begin(); it != target.end(); ++it) + { + if (source.find(it.key()) == source.end()) + { + // found a key that is not in this -> add it + const auto key = json_pointer::escape(it.key()); + result.push_back( + { + {"op", "add"}, + {"path", path + "/" + key}, + {"value", it.value()} + }); + } + } + + break; + } + + default: + { + // both primitive type: replace value + result.push_back( + { + {"op", "replace"}, + {"path", path}, + {"value", target} + }); + break; + } + } + } + + return result; + } + + /// @} +}; + + +///////////// +// presets // +///////////// + +/*! +@brief default JSON class + +This type is the default specialization of the @ref basic_json class which +uses the standard template types. + +@since version 1.0.0 +*/ +using json = basic_json<>; +} + + +/////////////////////// +// nonmember support // +/////////////////////// + +// specialization of std::swap, and std::hash +namespace std +{ +/*! +@brief exchanges the values of two JSON objects + +@since version 1.0.0 +*/ +template<> +inline void swap(nlohmann::json& j1, + nlohmann::json& j2) noexcept( + is_nothrow_move_constructible::value and + is_nothrow_move_assignable::value + ) +{ + j1.swap(j2); +} + +/// hash value for JSON objects +template<> +struct hash +{ + /*! + @brief return a hash value for a JSON object + + @since version 1.0.0 + */ + std::size_t operator()(const nlohmann::json& j) const + { + // a naive hashing via the string representation + const auto& h = hash(); + return h(j.dump()); + } +}; +} + +/*! +@brief user-defined string literal for JSON values + +This operator implements a user-defined string literal for JSON objects. It +can be used by adding `"_json"` to a string literal and returns a JSON object +if no parse error occurred. + +@param[in] s a string representation of a JSON object +@param[in] n the length of string @a s +@return a JSON object + +@since version 1.0.0 +*/ +inline nlohmann::json operator "" _json(const char* s, std::size_t n) +{ + return nlohmann::json::parse(s, s + n); +} + +/*! +@brief user-defined string literal for JSON pointer + +This operator implements a user-defined string literal for JSON Pointers. It +can be used by adding `"_json_pointer"` to a string literal and returns a JSON pointer +object if no parse error occurred. + +@param[in] s a string representation of a JSON Pointer +@param[in] n the length of string @a s +@return a JSON pointer object + +@since version 2.0.0 +*/ +inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n) +{ + return nlohmann::json::json_pointer(std::string(s, n)); +} + +// restore GCC/clang diagnostic settings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic pop +#endif + +#endif diff --git a/json_fwd.hpp b/json_fwd.hpp new file mode 100644 index 000000000..afe8a7deb --- /dev/null +++ b/json_fwd.hpp @@ -0,0 +1,30 @@ +#ifndef NLOHMANN_JSON_FWD_HPP +#define NLOHMANN_JSON_FWD_HPP + +#include +#include +#include +#include + +namespace nlohmann +{ + +template < + template class ObjectType, + template class ArrayType, + class StringType, + class BooleanType, + class NumberIntegerType, + class NumberUnsignedType, + class NumberFloatType, + template class AllocatorType + > +class basic_json; + +using json = basic_json; + +} // namespace nlohmann + +#endif // NLOHMANN_JSON_FWD_HPP + diff --git a/layer.hpp b/layer.hpp new file mode 100644 index 000000000..aa0f47e6b --- /dev/null +++ b/layer.hpp @@ -0,0 +1,15 @@ +#pragma once +#include "common.hpp" + +namespace horizon { + class Layer { + public : + Layer(int i, const std::string &n, const Color& c, bool r=false, bool cop=false):index(i), name(n), color(c), reverse(r), copper(cop){} + int index; + std::string name; + Color color; + bool reverse; + bool copper; + }; + +} diff --git a/object.hpp b/object.hpp new file mode 100644 index 000000000..1203bd166 --- /dev/null +++ b/object.hpp @@ -0,0 +1,32 @@ +#pragma once +#include "uuid.hpp" + +namespace horizon { + + class Object { + public : + virtual class Line *get_line(const UUID &uu) { + return nullptr; + } + virtual class Junction *get_junction(const UUID &uu) { + return nullptr; + } + virtual class Unit *get_unit(const UUID &uu) { + return nullptr; + } + virtual class SymbolPin *get_symbol_pin(const UUID &uu) { + return nullptr; + } + virtual class Entity *get_entity(const UUID &uu) { + return nullptr; + } + virtual class Net *get_net(const UUID &uu) { + return nullptr; + } + virtual class Symbol *get_symbol(const UUID &uu) { + return nullptr; + } + }; + + +} diff --git a/object_descr.cpp b/object_descr.cpp new file mode 100644 index 000000000..4b24ecd25 --- /dev/null +++ b/object_descr.cpp @@ -0,0 +1,85 @@ +#include "object_descr.hpp" + +namespace horizon { + const std::map object_descriptions = { + {ObjectType::SYMBOL_PIN, {"Symbol Pin", "Symbol Pins", { + {ObjectProperty::ID::NAME, {ObjectProperty::Type::STRING_RO, "Name"}}, + {ObjectProperty::ID::NAME_VISIBLE, {ObjectProperty::Type::BOOL, "Name visible"}}, + {ObjectProperty::ID::PAD_VISIBLE, {ObjectProperty::Type::BOOL, "Pad visible"}}, + {ObjectProperty::ID::LENGTH, {ObjectProperty::Type::LENGTH, "Length"}}, + }}}, + {ObjectType::JUNCTION, {"Junction", "Junctions", { + }}}, + {ObjectType::LINE_NET, {"Net line", "Net lines", { + }}}, + {ObjectType::BUS_LABEL, {"Bus label", "Bus labels", { + }}}, + {ObjectType::BUS_RIPPER, {"Bus ripper", "Bus rippers", { + }}}, + {ObjectType::SCHEMATIC_SYMBOL, {"Symbol", "Symbols", { + }}}, + {ObjectType::POWER_SYMBOL, {"Power symbol", "Power symbols", { + }}}, + {ObjectType::POLYGON_EDGE, {"Polygon edge", "Polygon edges", { + }}}, + {ObjectType::POLYGON_VERTEX, {"Polygon vertex", "Polygon vertices", { + }}}, + {ObjectType::POLYGON_ARC_CENTER, {"Polygon arc center", "Polygon arc centers", { + }}}, + {ObjectType::VIA, {"Via", "Vias", { + }}}, + {ObjectType::LINE, {"Line", "Lines", { + {ObjectProperty::ID::WIDTH, {ObjectProperty::Type::LENGTH, "Width"}}, + {ObjectProperty::ID::LAYER, {ObjectProperty::Type::LAYER, "Layer"}}, + }}}, + {ObjectType::ARC, {"Arc", "Arcs", { + {ObjectProperty::ID::WIDTH, {ObjectProperty::Type::LENGTH, "Width"}}, + {ObjectProperty::ID::LAYER, {ObjectProperty::Type::LAYER, "Layer"}}, + }}}, + {ObjectType::TEXT, {"Text", "Texts", { + {ObjectProperty::ID::SIZE, {ObjectProperty::Type::LENGTH, "Size"}}, + {ObjectProperty::ID::WIDTH, {ObjectProperty::Type::LENGTH, "Width"}}, + {ObjectProperty::ID::TEXT, {ObjectProperty::Type::STRING, "Text"}}, + {ObjectProperty::ID::LAYER, {ObjectProperty::Type::LAYER, "Layer"}}, + + }}}, + {ObjectType::COMPONENT, {"Component", "Components", { + {ObjectProperty::ID::REFDES, {ObjectProperty::Type::STRING, "Ref. Desig."}}, + {ObjectProperty::ID::VALUE, {ObjectProperty::Type::STRING, "Value"}}, + {ObjectProperty::ID::MPN, {ObjectProperty::Type::STRING_RO, "MPN"}}, + }}}, + {ObjectType::NET, {"Net", "Nets", { + {ObjectProperty::ID::NAME, {ObjectProperty::Type::STRING, "Name"}}, + {ObjectProperty::ID::IS_POWER, {ObjectProperty::Type::BOOL, "Is power net"}}, + {ObjectProperty::ID::NET_CLASS, {ObjectProperty::Type::NET_CLASS, "Net class"}}, + }}}, + {ObjectType::NET_LABEL, {"Net label", "Net labels", { + {ObjectProperty::ID::SIZE, {ObjectProperty::Type::LENGTH, "Size"}}, + {ObjectProperty::ID::OFFSHEET_REFS, {ObjectProperty::Type::BOOL, "Offsheet refs"}}, + {ObjectProperty::ID::NAME, {ObjectProperty::Type::STRING_RO, "Net name"}}, + }}}, + {ObjectType::POLYGON, {"Polygon", "Polygons", { + {ObjectProperty::ID::LAYER, {ObjectProperty::Type::LAYER, "Layer"}}, + }}}, + {ObjectType::HOLE, {"Hole", "Holes", { + {ObjectProperty::ID::DIAMETER, {ObjectProperty::Type::LENGTH, "Diameter"}}, + {ObjectProperty::ID::PLATED, {ObjectProperty::Type::BOOL, "Plated"}}, + }}}, + {ObjectType::PAD, {"Pad", "Pads", { + {ObjectProperty::ID::NAME, {ObjectProperty::Type::STRING, "Name"}}, + }}}, + {ObjectType::BOARD_PACKAGE, {"Package", "Packages", { + {ObjectProperty::ID::FLIPPED, {ObjectProperty::Type::BOOL, "Flipped"}}, + {ObjectProperty::ID::REFDES, {ObjectProperty::Type::STRING_RO, "Ref. Desig."}}, + {ObjectProperty::ID::NAME, {ObjectProperty::Type::STRING_RO, "Package"}}, + }}}, + {ObjectType::TRACK, {"Track", "Tracks", { + {ObjectProperty::ID::WIDTH, {ObjectProperty::Type::LENGTH, "Width"}}, + {ObjectProperty::ID::LAYER, {ObjectProperty::Type::LAYER_COPPER, "Layer"}}, + {ObjectProperty::ID::NAME, {ObjectProperty::Type::STRING_RO, "Net"}}, + {ObjectProperty::ID::NET_CLASS, {ObjectProperty::Type::STRING_RO, "Net class"}}, + {ObjectProperty::ID::WIDTH_FROM_NET_CLASS, {ObjectProperty::Type::BOOL, "Width from net class"}}, + }}}, + }; + +} diff --git a/object_descr.hpp b/object_descr.hpp new file mode 100644 index 000000000..facd46fba --- /dev/null +++ b/object_descr.hpp @@ -0,0 +1,33 @@ +#pragma once +#include "common.hpp" +#include + +namespace horizon { + class ObjectProperty { + public : + enum class Type {BOOL, INT, STRING, STRING_RO, LENGTH, LAYER, LAYER_COPPER, NET_CLASS}; + enum class ID {NAME, NAME_VISIBLE, PAD_VISIBLE, LENGTH, SIZE, TEXT, REFDES, VALUE, IS_POWER, OFFSHEET_REFS, WIDTH, LAYER, + DIAMETER, PLATED, FLIPPED, NET_CLASS, WIDTH_FROM_NET_CLASS, MPN + }; + ObjectProperty(Type t, const std::string &l): type(t), label(l) {} + + Type type; + std::string label; + }; + + class ObjectDescription { + public : + ObjectDescription(const std::string &n, const std::string &n_pl, const std::map &props): + name(n), + name_pl(n_pl), + properties(props) + {} + + std::string name; + std::string name_pl; + const std::map properties; + }; + + extern const std::map object_descriptions; + +} diff --git a/obstacle/canvas_obstacle.cpp b/obstacle/canvas_obstacle.cpp new file mode 100644 index 000000000..11fd71796 --- /dev/null +++ b/obstacle/canvas_obstacle.cpp @@ -0,0 +1,52 @@ +#include "canvas_obstacle.hpp" +#include "assert.h" +#include "core/core_board.hpp" + +namespace horizon { + CanvasObstacle::CanvasObstacle() : Canvas::Canvas() { + img_mode = true; + } + void CanvasObstacle::request_push() {} + + void CanvasObstacle::img_net(const Net *n) { + net = n; + } + + void CanvasObstacle::img_polygon(const Polygon &poly) { + if(net == routing_net) + return; + if(!net) + return; + if(poly.layer != routing_layer) + return; + + ClipperLib::Path t; + t.reserve(poly.vertices.size()); + for(const auto &it: poly.vertices) { + auto p = transform.transform(it.position); + t.emplace_back(p.x, p.y); + //std::cout << it.position.x << " " << it.position.y << std::endl; + } + if(ClipperLib::Orientation(t)) { + std::reverse(t.begin(), t.end()); + } + ClipperLib::ClipperOffset ofs; + ofs.ArcTolerance = 10e3; + ofs.AddPath(t, ClipperLib::jtRound, ClipperLib::etClosedPolygon); + + ClipperLib::Paths t_ofs; + Cores cores(core); + auto clearance = &cores.b->get_constraints()->default_clearance; + if(net && routing_net) + clearance = cores.b->get_constraints()->get_clearance(net->net_class, routing_net->net_class); + uint64_t expand = clearance->routing_clearance; + ofs.Execute(t_ofs, expand+routing_width/2); + assert(t_ofs.size()==1); + + + clipper.AddPath(t_ofs[0], ClipperLib::ptSubject, true); + + } + + +} diff --git a/obstacle/canvas_obstacle.hpp b/obstacle/canvas_obstacle.hpp new file mode 100644 index 000000000..998373391 --- /dev/null +++ b/obstacle/canvas_obstacle.hpp @@ -0,0 +1,24 @@ +#pragma once +#include "canvas/canvas.hpp" +#include "clipper/clipper.hpp" + + +namespace horizon { + class CanvasObstacle: public Canvas { + public : + CanvasObstacle(); + void push() override {} + void request_push() override; + const Net *routing_net = nullptr; + int routing_layer = 0; + uint64_t routing_width = 0; + ClipperLib::Clipper clipper; + + private : + + const Net *net = nullptr; + virtual void img_net(const Net *net); + virtual void img_polygon(const Polygon &poly); + + }; +} diff --git a/package/pad.cpp b/package/pad.cpp new file mode 100644 index 000000000..b9e72cd34 --- /dev/null +++ b/package/pad.cpp @@ -0,0 +1,29 @@ +#include "pad.hpp" +#include "json.hpp" + +namespace horizon { + + Pad::Pad(const UUID &uu, const json &j, Pool &pool): + uuid(uu), + pool_padstack(pool.get_padstack(j.at("padstack").get())), + padstack(*pool_padstack), + placement(j.at("placement")), + name(j.at("name").get()) + { + } + Pad::Pad(const UUID &uu, Padstack *ps): uuid(uu), pool_padstack(ps), padstack(*ps) {} + + json Pad::serialize() const { + json j; + j["padstack"] = (std::string)pool_padstack->uuid; + j["placement"] = placement.serialize(); + j["name"] = name; + + + return j; + } + + UUID Pad::get_uuid() const { + return uuid; + } +} diff --git a/package/pad.hpp b/package/pad.hpp new file mode 100644 index 000000000..69fe9e2fa --- /dev/null +++ b/package/pad.hpp @@ -0,0 +1,35 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "unit.hpp" +#include "symbol.hpp" +#include "block.hpp" +#include "uuid_ptr.hpp" +#include "placement.hpp" +#include "uuid_provider.hpp" +#include "pool.hpp" +#include +#include +#include + +namespace horizon { + using json = nlohmann::json; + + class Pad: public UUIDProvider { + public : + Pad(const UUID &uu, const json &, Pool &pool); + Pad(const UUID &uu, Padstack *ps); + UUID uuid; + Padstack *pool_padstack; + Padstack padstack; + Placement placement; + std::string name; + + uuid_ptr net = nullptr; + UUID net_segment; + + virtual UUID get_uuid() const; + json serialize() const; + }; + +} diff --git a/pool-update-parametric/pool-update-parametric.cpp b/pool-update-parametric/pool-update-parametric.cpp new file mode 100644 index 000000000..9c02579db --- /dev/null +++ b/pool-update-parametric/pool-update-parametric.cpp @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "unit.hpp" +#include "sqlite.hpp" +#include "pool.hpp" +#include "package.hpp" +#include "part.hpp" +#include "json.hpp" + +using json = nlohmann::json; + +bool endswidth(const std::string &haystack, const std::string &needle) { + return (haystack.size()-haystack.rfind(needle)) == needle.size(); +} + + +void update_resistor(SQLite::Database &db, const horizon::Part *part) { + SQLite::Query q(db, "INSERT INTO resistors (part, value, pmax, tolerance) VALUES ($part, $value, $pmax, $tolerance)"); + q.bind("$part", part->uuid); + q.bind("$value", part->parametric.at("value")); + q.bind("$pmax", part->parametric.at("pmax")); + q.bind("$tolerance", part->parametric.at("tolerance")); + q.step(); +} +void update_capacitor(SQLite::Database &db, const horizon::Part *part) { + SQLite::Query q(db, "INSERT INTO capacitors (part, value, wvdc, characteristic) VALUES ($part, $value, $wvdc, $characteristic)"); + q.bind("$part", part->uuid); + q.bind("$value", part->parametric.at("value")); + q.bind("$wvdc", part->parametric.at("wvdc")); + q.bind("$characteristic", part->parametric.at("characteristic")); + q.step(); +} + +int main(int c_argc, char *c_argv[]) { + std::vector argv; + for(int i = 0; iget_size()+1};//null byte + auto data = (const char*)bytes->get_data(size); + db_parametric.execute(data); + std::cout << "created db from schema" << std::endl; + } + } + + + horizon::Pool pool(pool_base_path); + + db_parametric.execute("BEGIN TRANSACTION"); + db_parametric.execute("DELETE FROM resistors"); + db_parametric.execute("DELETE FROM capacitors"); + + SQLite::Query q(db, "SELECT uuid from parts"); + while(q.step()) { + auto part = pool.get_part(q.get(0)); + if(part->parametric.count("table")) { + std::string table = part->parametric.at("table"); + if(table == "resistors") { + update_resistor(db_parametric, part); + } + if(table == "capacitors") { + update_capacitor(db_parametric, part); + } + } + } + db_parametric.execute("COMMIT"); + + + + + return 0; +} diff --git a/pool-update-parametric/schema.sql b/pool-update-parametric/schema.sql new file mode 100644 index 000000000..b374bd17b --- /dev/null +++ b/pool-update-parametric/schema.sql @@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS "resistors" ( + `part` TEXT NOT NULL UNIQUE, + `value` REAL, + `pmax` REAL, + `tolerance` REAL, + PRIMARY KEY(`part`) +); +CREATE TABLE `capacitors` ( + `part` TEXT NOT NULL UNIQUE, + `value` REAL, + `wvdc` REAL, + `characteristic` TEXT, + PRIMARY KEY(`part`) +); diff --git a/pool-update/pool-update.cpp b/pool-update/pool-update.cpp new file mode 100644 index 000000000..3f075051e --- /dev/null +++ b/pool-update/pool-update.cpp @@ -0,0 +1,264 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "unit.hpp" +#include "sqlite.hpp" +#include "pool.hpp" +#include "package.hpp" +#include "part.hpp" +#include "json.hpp" + +using json = nlohmann::json; + +bool endswidth(const std::string &haystack, const std::string &needle) { + return (haystack.size()-haystack.rfind(needle)) == needle.size(); +} + +void update_units(SQLite::Database &db, const std::string &directory, const std::string &prefix="") { + if(prefix.size()==0) { + db.execute("DELETE from units"); + } + Glib::Dir dir(directory); + for(const auto &it: dir) { + std::string filename = Glib::build_filename(directory, it); + if(endswidth(it, ".json")) { + auto unit = horizon::Unit::new_from_file(filename); + SQLite::Query q(db, "INSERT INTO units (uuid, name, filename) VALUES ($uuid, $name, $filename)"); + q.bind("$uuid", unit.uuid); + q.bind("$name", unit.name); + q.bind("$filename", Glib::build_filename(prefix, it)); + q.step(); + } + else if(Glib::file_test(filename, Glib::FILE_TEST_IS_DIR)) { + update_units(db, filename, Glib::build_filename(prefix, it)); + } + } +} + +void update_entities(SQLite::Database &db, horizon::Pool &pool, const std::string &directory, const std::string &prefix="") { + if(prefix.size()==0) { + db.execute("DELETE from entities"); + } + Glib::Dir dir(directory); + for(const auto &it: dir) { + std::string filename = Glib::build_filename(directory, it); + if(endswidth(it, ".json")) { + auto entity = horizon::Entity::new_from_file(filename, pool); + SQLite::Query q(db, "INSERT INTO entities (uuid, name, filename, n_gates, prefix) VALUES ($uuid, $name, $filename, $n_gates, $prefix)"); + q.bind("$uuid", entity.uuid); + q.bind("$name", entity.name); + q.bind("$n_gates", entity.gates.size()); + q.bind("$prefix", entity.prefix); + q.bind("$filename", Glib::build_filename(prefix, it)); + q.step(); + for(const auto &it_tag: entity.tags) { + SQLite::Query q2(db, "INSERT into tags (tag, uuid, type) VALUES ($tag, $uuid, 'entity')"); + q2.bind("$uuid", entity.uuid); + q2.bind("$tag", it_tag); + q2.step(); + } + } + else if(Glib::file_test(filename, Glib::FILE_TEST_IS_DIR)) { + update_entities(db, pool, filename, Glib::build_filename(prefix,it)); + } + } +} + +void update_symbols(SQLite::Database &db, horizon::Pool &pool, const std::string &directory, const std::string &prefix="") { + if(prefix.size() == 0) { + db.execute("DELETE from symbols"); + } + Glib::Dir dir(directory); + for(const auto &it: dir) { + std::string filename = Glib::build_filename(directory, it); + if(endswidth(it, ".json")) { + auto symbol = horizon::Symbol::new_from_file(filename, pool); + SQLite::Query q(db, "INSERT INTO symbols (uuid, name, filename, unit) VALUES ($uuid, $name, $filename, $unit)"); + q.bind("$uuid", symbol.uuid); + q.bind("$name", symbol.name); + q.bind("$unit", symbol.unit->uuid); + q.bind("$filename", Glib::build_filename(prefix, it)); + q.step(); + } + else if(Glib::file_test(filename, Glib::FILE_TEST_IS_DIR)) { + update_symbols(db, pool, filename, Glib::build_filename(prefix,it)); + } + } +} + +void update_padstacks(SQLite::Database &db, const std::string &directory, const std::string &prefix="") { + if(prefix.size()==0) { + db.execute("DELETE from padstacks"); + } + Glib::Dir dir(directory); + for(const auto &it: dir) { + auto pkgpath = Glib::build_filename(directory, it); + auto pkgfilename = Glib::build_filename(pkgpath, "package.json"); + if(Glib::file_test(pkgfilename, Glib::FileTest::FILE_TEST_IS_REGULAR)) { + horizon::UUID pkg_uuid; + //we'll have to parse the package manually, since we don't have padstacks yet + { + std::ifstream ifs(Glib::build_filename(pkgpath, "package.json")); + json j; + if(!ifs.is_open()) { + throw std::runtime_error("package not opened"); + } + ifs>>j; + ifs.close(); + pkg_uuid = j.at("uuid"); + } + + auto padstacks_path = Glib::build_filename(pkgpath, "padstacks"); + Glib::Dir dir2(padstacks_path); + for(const auto &it2: dir2) { + if(endswidth(it2, ".json")) { + std::string filename = Glib::build_filename(padstacks_path, it2); + auto padstack = horizon::Padstack::new_from_file(filename); + SQLite::Query q(db, "INSERT INTO padstacks (uuid, name, filename, package) VALUES ($uuid, $name, $filename, $package)"); + q.bind("$uuid", padstack.uuid); + q.bind("$name", padstack.name); + q.bind("$package", pkg_uuid); + q.bind("$filename", Glib::build_filename(prefix, it, "padstacks", it2)); + q.step(); + } + } + } + else if(Glib::file_test(pkgpath, Glib::FILE_TEST_IS_DIR)) { + update_padstacks(db, pkgpath, Glib::build_filename(prefix,it)); + } + + } +} + +void update_packages(SQLite::Database &db, horizon::Pool &pool, const std::string &directory, const std::string &prefix="") { + if(prefix.size()==0) { + db.execute("DELETE from packages"); + } + Glib::Dir dir(directory); + for(const auto &it: dir) { + auto pkgpath = Glib::build_filename(directory, it); + auto pkgfilename = Glib::build_filename(pkgpath, "package.json"); + if(Glib::file_test(pkgfilename, Glib::FileTest::FILE_TEST_IS_REGULAR)) { + std::string filename = Glib::build_filename(pkgpath, "package.json"); + auto package = horizon::Package::new_from_file(filename, pool); + SQLite::Query q(db, "INSERT INTO packages (uuid, name, filename, n_pads) VALUES ($uuid, $name, $filename, $n_pads)"); + q.bind("$uuid", package.uuid); + q.bind("$name", package.name); + q.bind("$n_pads", package.pads.size()); + q.bind("$filename", Glib::build_filename(prefix, it, "package.json")); + q.step(); + for(const auto &it_tag: package.tags) { + SQLite::Query q2(db, "INSERT into tags (tag, uuid, type) VALUES ($tag, $uuid, 'package')"); + q2.bind("$uuid", package.uuid); + q2.bind("$tag", it_tag); + q2.step(); + } + } + else if(Glib::file_test(pkgpath, Glib::FILE_TEST_IS_DIR)) { + update_packages(db, pool, pkgpath, Glib::build_filename(prefix,it)); + } + } +} + +bool update_parts(SQLite::Database &db, horizon::Pool &pool, const std::string &directory, const std::string &prefix="") { + if(prefix.size()==0) + db.execute("DELETE from parts"); + + Glib::Dir dir(directory); + bool skipped = true; + while(skipped) { + skipped = false; + for(const auto &it: dir) { + std::string filename = Glib::build_filename(directory, it); + if(endswidth(it, ".json")) { + bool skipthis = false; + { + std::ifstream ifs(filename); + json j; + if(!ifs.is_open()) { + throw std::runtime_error("part not opened"); + } + ifs>>j; + ifs.close(); + if(j.count("base")) { + horizon::UUID base_uuid = j.at("base").get(); + SQLite::Query q(db, "SELECT uuid from parts WHERE uuid = $uuid"); + q.bind("$uuid", base_uuid); + if(!q.step()) { //not found + skipthis = true; + skipped = true; + } + } + } + if(!skipthis) { + auto part = horizon::Part::new_from_file(filename, pool); + SQLite::Query q(db, "INSERT INTO parts (uuid, MPN, manufacturer, entity, package, filename) VALUES ($uuid, $MPN, $manufacturer, $entity, $package, $filename)"); + q.bind("$uuid", part.uuid); + q.bind("$MPN", part.get_MPN()); + q.bind("$manufacturer", part.get_manufacturer()); + q.bind("$package", part.package->uuid); + q.bind("$entity", part.entity->uuid); + q.bind("$filename", Glib::build_filename(prefix, it)); + q.step(); + + for(const auto &it_tag: part.get_tags()) { + SQLite::Query q2(db, "INSERT into tags (tag, uuid, type) VALUES ($tag, $uuid, 'part')"); + q2.bind("$uuid", part.uuid); + q2.bind("$tag", it_tag); + q2.step(); + } + } + } + else if(Glib::file_test(filename, Glib::FILE_TEST_IS_DIR)) { + if(update_parts(db, pool, filename, Glib::build_filename(prefix,it))) + skipped = true; + } + } + if(prefix.size()>0) + break; + } + return skipped; + +} + +int main(int c_argc, char *c_argv[]) { + std::vector argv; + for(int i = 0; iget_size()+1};//null byte + auto data = (const char*)bytes->get_data(size); + db.execute(data); + std::cout << "created db from schema" << std::endl; + } + } + + + horizon::Pool pool(pool_base_path); + + + db.execute("BEGIN TRANSACTION"); + db.execute("DELETE FROM tags"); + update_units(db, Glib::build_filename(pool_base_path, "units")); + update_entities(db, pool, Glib::build_filename(pool_base_path, "entities")); + update_symbols(db, pool, Glib::build_filename(pool_base_path, "symbols")); + update_padstacks(db, Glib::build_filename(pool_base_path, "packages")); + update_packages(db, pool, Glib::build_filename(pool_base_path, "packages")); + update_parts(db, pool, Glib::build_filename(pool_base_path, "parts")); + db.execute("COMMIT"); + + + return 0; +} diff --git a/pool-update/schema.sql b/pool-update/schema.sql new file mode 100644 index 000000000..50fbe0db5 --- /dev/null +++ b/pool-update/schema.sql @@ -0,0 +1,50 @@ +CREATE TABLE IF NOT EXISTS "units" ( + `uuid` TEXT NOT NULL UNIQUE, + `name` TEXT NOT NULL, + `filename` TEXT NOT NULL, + PRIMARY KEY(`uuid`) +); +CREATE TABLE IF NOT EXISTS "entities" ( + `uuid` TEXT NOT NULL UNIQUE, + `name` TEXT NOT NULL, + `n_gates` INTEGER NOT NULL, + `prefix` TEXT NOT NULL, + `filename` TEXT NOT NULL, + PRIMARY KEY(`uuid`) +); +CREATE TABLE IF NOT EXISTS "symbols" ( + `uuid` TEXT NOT NULL UNIQUE, + `unit` TEXT NOT NULL, + `name` TEXT NOT NULL, + `filename` TEXT NOT NULL, + PRIMARY KEY(`uuid`) +); +CREATE TABLE IF NOT EXISTS "packages" ( + `uuid` TEXT NOT NULL UNIQUE, + `name` TEXT NOT NULL, + `n_pads` INTEGER NOT NULL, + `filename` TEXT NOT NULL, + PRIMARY KEY(`uuid`) +); +CREATE TABLE IF NOT EXISTS "padstacks" ( + `uuid` TEXT NOT NULL UNIQUE, + `name` TEXT NOT NULL, + `package` TEXT NOT NULL, + `filename` TEXT NOT NULL, + PRIMARY KEY(`uuid`) +); +CREATE TABLE IF NOT EXISTS "parts" ( + `uuid` TEXT NOT NULL UNIQUE, + `MPN` TEXT, + `manufacturer` TEXT, + `entity` TEXT NOT NULL, + `package` TEXT NOT NULL, + `filename` TEXT, + PRIMARY KEY(`uuid`) +); +CREATE TABLE `tags` ( + `tag` TEXT NOT NULL, + `uuid` TEXT NOT NULL, + `type` TEXT NOT NULL, + PRIMARY KEY(`tag`,`uuid`) +); diff --git a/pool-util/part-editor.cpp b/pool-util/part-editor.cpp new file mode 100644 index 000000000..d11fec8c3 --- /dev/null +++ b/pool-util/part-editor.cpp @@ -0,0 +1,605 @@ +#include "part-editor.hpp" +#include "dialogs/pool_browser_entity.hpp" +#include "dialogs/pool_browser_package.hpp" +#include "dialogs/pool_browser_part.hpp" +#include "json.hpp" +#include "util.hpp" +#include + +using json = nlohmann::json; + + +class EntryWithInheritance: public Gtk::Box { + public: + EntryWithInheritance() :Glib::ObjectBase (typeid(EntryWithInheritance)), Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0), + p_property_inherit(*this, "inherit"), p_property_can_inherit(*this, "can-inherit") { + entry = Gtk::manage(new Gtk::Entry); + button = Gtk::manage(new Gtk::ToggleButton("Inherit")); + + pack_start(*entry, true, true, 0); + pack_start(*button, false, false, 0); + get_style_context()->add_class("linked"); + entry->show(); + button->show(); + + inh_binding = Glib::Binding::bind_property(button->property_active(), property_inherit(), Glib::BINDING_BIDIRECTIONAL); + property_inherit().signal_changed().connect([this] { + entry->set_sensitive(!property_inherit()); + if(property_inherit()) { + text_this = entry->get_text(); + entry->set_text(text_inherit); + } + else { + entry->set_text(text_this); + } + }); + + property_can_inherit().signal_changed().connect([this] { + button->set_sensitive(property_can_inherit()); + if(!property_can_inherit()) { + property_inherit() = false; + } + }); + + } + void set_text_inherit(const std::string &s) { + text_inherit = s; + if(property_inherit()) { + entry->set_text(text_inherit); + } + } + + void set_text_this(const std::string &s) { + text_this = s; + if(!property_inherit()) { + entry->set_text(text_this); + } + } + + std::pair get_as_pair() { + if(property_inherit()) { + return {true, text_this}; + } + else { + return {false, entry->get_text()}; + } + } + + Gtk::Entry *entry=nullptr; + Gtk::ToggleButton *button = nullptr; + + + Glib::PropertyProxy property_inherit() { return p_property_inherit.get_proxy(); } + Glib::PropertyProxy property_can_inherit() { return p_property_can_inherit.get_proxy(); } + + private : + Glib::Property p_property_inherit; + Glib::Property p_property_can_inherit; + Glib::RefPtr inh_binding; + std::string text_inherit; + std::string text_this; + +}; + +class PartEditorWindow: public Gtk::Window { + public: + PartEditorWindow(BaseObjectType* cobject, const Glib::RefPtr& x); + static PartEditorWindow* create(); + EntryWithInheritance *w_mpn; + EntryWithInheritance *w_manufacturer; + EntryWithInheritance *w_value; + + std::map attr_editors; + + Gtk::Entry *w_tags; + Gtk::Entry *w_tags_inherited; + Gtk::ToggleButton *w_tags_inherit; + Gtk::Box *w_entity_button_box; + Gtk::Box *w_package_button_box; + Gtk::Box *w_part_button_box; + Gtk::TreeView *w_tv_pins; + Gtk::TreeView *w_tv_pads; + Gtk::Button *w_button_map; + Gtk::Button *w_button_unmap; + Gtk::Button *w_button_save; + Gtk::Label *w_pin_stat; + Gtk::Label *w_pad_stat; + Gtk::TextView *w_parametric; + Gtk::Button *w_parametric_from_base; + + private : + + +}; + + +PartEditorWindow::PartEditorWindow(BaseObjectType* cobject, const Glib::RefPtr& x) : + Gtk::Window(cobject) { + + show_all(); +} + +PartEditorWindow* PartEditorWindow::create() { + PartEditorWindow* w; + Glib::RefPtr x = Gtk::Builder::create(); + x->add_from_resource("/net/carrotIndustries/horizon/pool-util/part-editor.ui"); + x->get_widget_derived("window", w); + + auto add_entry = [x, w](const char *name) { + Gtk::Box *t; + x->get_widget(name, t); + auto e = Gtk::manage(new EntryWithInheritance); + e->show(); + t->pack_start(*e, true, true, 0); + return e; + }; + + w->w_mpn = add_entry("part_mpn_box"); + w->w_value =add_entry("part_value_box"); + w->w_manufacturer =add_entry("part_manufacturer_box"); + w->attr_editors.emplace(horizon::Part::Attribute::MPN, w->w_mpn); + w->attr_editors.emplace(horizon::Part::Attribute::VALUE, w->w_value); + w->attr_editors.emplace(horizon::Part::Attribute::MANUFACTURER, w->w_manufacturer); + + x->get_widget("part_tags", w->w_tags); + x->get_widget("part_tags_inherit", w->w_tags_inherit); + x->get_widget("part_tags_inherited", w->w_tags_inherited); + x->get_widget("entity_button_box", w->w_entity_button_box); + x->get_widget("package_button_box", w->w_package_button_box); + x->get_widget("part_button_box", w->w_part_button_box); + x->get_widget("tv_pins", w->w_tv_pins); + x->get_widget("tv_pads", w->w_tv_pads); + x->get_widget("button_map", w->w_button_map); + x->get_widget("button_unmap", w->w_button_unmap); + x->get_widget("button_save", w->w_button_save); + x->get_widget("pad_stat", w->w_pad_stat); + x->get_widget("pin_stat", w->w_pin_stat); + x->get_widget("parametric", w->w_parametric); + x->get_widget("copy_parametric_from_base", w->w_parametric_from_base); + return w; +} + + + +PartEditor::PartEditor(const std::string &fn, bool create): filename(fn), pool(Glib::getenv("HORIZON_POOL")), part(create?horizon::UUID::random():horizon::Part::new_from_file(filename, pool)) {} + + +class EntityButton: public Gtk::Button { + public: + EntityButton(horizon::Pool &p):Glib::ObjectBase (typeid(EntityButton)), Gtk::Button("fixme"), p_property_selected_uuid(*this, "selected-uuid"), pool(p) { + update_label(); + property_selected_uuid().signal_changed().connect([this]{update_label();}); + } + Glib::PropertyProxy property_selected_uuid() { return p_property_selected_uuid.get_proxy(); } + + protected : + Glib::Property p_property_selected_uuid; + horizon::Pool &pool; + + void on_clicked() override { + Gtk::Button::on_clicked(); + auto top = dynamic_cast(get_ancestor(GTK_TYPE_WINDOW)); + horizon::PoolBrowserDialogEntity pb(top, &pool); + pb.run(); + if(pb.selection_valid) { + p_property_selected_uuid = pb.selected_uuid; + update_label(); + } + } + + void update_label() { + horizon::UUID uu = p_property_selected_uuid; + if(uu) { + set_label(pool.get_entity(uu)->name); + } + else { + set_label("no entity"); + } + } +}; + +class PackageButton: public Gtk::Button { + public: + PackageButton(horizon::Pool &p):Glib::ObjectBase (typeid(PackageButton)), Gtk::Button("fixme"), p_property_selected_uuid(*this, "selected-uuid"), pool(p) { + update_label(); + property_selected_uuid().signal_changed().connect([this]{update_label();}); + } + Glib::PropertyProxy property_selected_uuid() { return p_property_selected_uuid.get_proxy(); } + + protected : + Glib::Property p_property_selected_uuid; + horizon::Pool &pool; + + void on_clicked() override { + Gtk::Button::on_clicked(); + auto top = dynamic_cast(get_ancestor(GTK_TYPE_WINDOW)); + horizon::PoolBrowserDialogPackage pb(top, &pool); + pb.run(); + if(pb.selection_valid) { + p_property_selected_uuid = pb.selected_uuid; + update_label(); + } + } + + void update_label() { + horizon::UUID uu = p_property_selected_uuid; + if(uu) { + set_label(pool.get_package(uu)->name); + } + else { + set_label("no package"); + } + } +}; + +class PartButton: public Gtk::Button { + public: + PartButton(horizon::Pool &p):Glib::ObjectBase (typeid(PartButton)), Gtk::Button("fixme"), p_property_selected_uuid(*this, "selected-uuid"), pool(p) { + update_label(); + property_selected_uuid().signal_changed().connect([this]{update_label();}); + } + Glib::PropertyProxy property_selected_uuid() { return p_property_selected_uuid.get_proxy(); } + + protected : + Glib::Property p_property_selected_uuid; + horizon::Pool &pool; + + void on_clicked() override { + Gtk::Button::on_clicked(); + auto top = dynamic_cast(get_ancestor(GTK_TYPE_WINDOW)); + horizon::PoolBrowserDialogPart pb(top, &pool, horizon::UUID()); + pb.run(); + if(pb.selection_valid) { + p_property_selected_uuid = pb.selected_uuid; + update_label(); + } + } + + void update_label() { + horizon::UUID uu = p_property_selected_uuid; + if(uu) { + set_label(pool.get_part(uu)->get_MPN()); + } + else { + set_label("no part"); + } + } +}; + +void PartEditor::update_mapped() { + std::set> pins_mapped; + int n_pads_not_mapped = 0; + for(const auto &it: pad_store->children()) { + if(it[pad_list_columns.gate_uuid] != horizon::UUID()) { + pins_mapped.emplace(it[pad_list_columns.gate_uuid], it[pad_list_columns.pin_uuid]); + } + else { + n_pads_not_mapped++; + } + } + for(auto &it: pin_store->children()) { + if(pins_mapped.count({it[pin_list_columns.gate_uuid], it[pin_list_columns.pin_uuid]})) { + it[pin_list_columns.mapped] = true; + } + else { + it[pin_list_columns.mapped] = false; + } + } + win->w_pin_stat->set_text(std::to_string(pin_store->children().size()-pins_mapped.size()) + " pins not mapped"); + win->w_pad_stat->set_text(std::to_string(n_pads_not_mapped) + " pads not mapped"); +} + +void PartEditor::update_treeview() { + if(!part.entity || !part.package) + return; + + pin_store->freeze_notify(); + pad_store->freeze_notify(); + + pin_store->clear(); + pad_store->clear(); + + for(const auto &it_gate: part.entity->gates) { + for(const auto &it_pin: it_gate.second.unit->pins) { + Gtk::TreeModel::Row row = *(pin_store->append()); + row[pin_list_columns.gate_uuid] = it_gate.first; + row[pin_list_columns.gate_name] = it_gate.second.name; + row[pin_list_columns.pin_uuid] = it_pin.first; + row[pin_list_columns.pin_name] = it_pin.second.primary_name; + } + } + + for(const auto &it: part.package->pads) { + Gtk::TreeModel::Row row = *(pad_store->append()); + row[pad_list_columns.pad_uuid] = it.first; + row[pad_list_columns.pad_name] = it.second.name; + if(part.pad_map.count(it.first)) { + const auto &x =part.pad_map.at(it.first); + row[pad_list_columns.gate_uuid] = x.gate->uuid; + row[pad_list_columns.gate_name] = x.gate->name; + row[pad_list_columns.pin_uuid] = x.pin->uuid; + row[pad_list_columns.pin_name] = x.pin->primary_name; + } + } + + pad_store->thaw_notify(); + pin_store->thaw_notify(); + + update_mapped(); +} + +static void load_entry_base(EntryWithInheritance *e, horizon::Part *part, horizon::Part::Attribute a) { + if(part->base) { + e->set_text_inherit(part->base->get_attribute(a)); + } +} + + +static void load_entry(EntryWithInheritance *e, horizon::Part *part, horizon::Part::Attribute a) { + e->set_text_this(part->attributes.at(a).second); + e->property_inherit() = part->attributes.at(a).first; + load_entry_base(e, part, a); +} + +void PartEditor::update_tags_inherit() { + if(part.base) { + std::stringstream s; + std::copy(part.base->tags.begin(), part.base->tags.end(), std::ostream_iterator(s, " ")); + win->w_tags_inherited->set_text(s.str()); + } + else { + win->w_tags_inherited->set_text(""); + } + win->w_tags_inherit->set_active(part.inherit_tags); + win->w_tags_inherit->set_sensitive(part.base); +} + +static std::string serialize_parametric(const std::map &p) { + using namespace YAML; + Emitter em; + em << BeginMap; + if(p.count("table")) { + em << Key << "table" << Value << p.at("table"); + } + for(const auto &it: p) { + if(it.first != "table") + em << Key << it.first << Value << it.second; + } + em << EndMap; + return em.c_str(); +} + +void PartEditor::run() { + auto app = Gtk::Application::create("net.carrotIndustries.horizon.PartEditor", Gio::APPLICATION_NON_UNIQUE); + + win = PartEditorWindow::create(); + for(auto &it: win->attr_editors) { + load_entry(it.second, &part, it.first); + } + update_tags_inherit(); + win->w_parametric->get_buffer()->set_text(serialize_parametric(part.parametric)); + win->w_parametric_from_base->set_sensitive(part.base); + + win->w_button_save->signal_clicked().connect(sigc::mem_fun(this, &PartEditor::save)); + + { + std::stringstream s; + std::copy(part.tags.begin(), part.tags.end(), std::ostream_iterator(s, " ")); + win->w_tags->set_text(s.str()); + } + + entity_button = Gtk::manage(new EntityButton(pool)); + entity_button->show(); + if(part.entity) + entity_button->property_selected_uuid() = part.entity->uuid; + win->w_entity_button_box->pack_start(*entity_button, true, true, 0); + + package_button = Gtk::manage(new PackageButton(pool)); + package_button->show(); + if(part.package) + package_button->property_selected_uuid() = part.package->uuid; + win->w_package_button_box->pack_start(*package_button, true, true, 0); + + part_button = Gtk::manage(new PartButton(pool)); + part_button->show(); + part_button->property_selected_uuid() = part.base?part.base->uuid:horizon::UUID(); + win->w_part_button_box->pack_start(*part_button, true, true, 0); + + pin_store = Gtk::ListStore::create(pin_list_columns); + win->w_tv_pins->set_model(pin_store); + + win->w_tv_pins->append_column("Gate", pin_list_columns.gate_name); + win->w_tv_pins->append_column("Pin", pin_list_columns.pin_name); + { + auto cr = Gtk::manage(new Gtk::CellRendererPixbuf()); + cr->property_icon_name() = "object-select-symbolic"; + cr->property_xalign() = 0; + auto tvc = Gtk::manage(new Gtk::TreeViewColumn("Mapped", *cr)); + tvc->add_attribute(cr->property_visible(), pin_list_columns.mapped); + win->w_tv_pins->append_column(*tvc); + } + + win->w_tv_pins->get_column(0)->set_sort_column(pin_list_columns.gate_name); + win->w_tv_pins->get_column(1)->set_sort_column(pin_list_columns.pin_name); + + pad_store = Gtk::ListStore::create(pad_list_columns); + pad_store->set_sort_func(pad_list_columns.pad_name, [this](const Gtk::TreeModel::iterator &ia, const Gtk::TreeModel::iterator &ib) { + Gtk::TreeModel::Row ra = *ia; + Gtk::TreeModel::Row rb = *ib; + Glib::ustring a = ra[pad_list_columns.pad_name]; + Glib::ustring b = rb[pad_list_columns.pad_name]; + auto ca = g_utf8_collate_key_for_filename(a.c_str(), -1); + auto cb = g_utf8_collate_key_for_filename(b.c_str(), -1); + auto r = strcmp(ca, cb); + g_free(ca); + g_free(cb); + return r; + }); + win->w_tv_pads->set_model(pad_store); + win->w_tv_pads->append_column("Pad", pad_list_columns.pad_name); + /*{ + auto cr = Gtk::manage(new Gtk::CellRendererText()); + win->w_tv_pads->append_column("Pad", *cr); + win->w_tv_pads->get_column(0)->set_cell_data_func(*cr, [this](Gtk::CellRenderer *c, const Gtk::TreeModel::iterator &it){ + auto ct = dynamic_cast(c); + ct->property_text() = part.package->pads.at(it[]) + }); + }*/ + win->w_tv_pads->append_column("Gate", pad_list_columns.gate_name); + win->w_tv_pads->append_column("Pin", pad_list_columns.pin_name); + win->w_tv_pads->get_column(0)->set_sort_column(pad_list_columns.pad_name); + + + update_treeview(); + + win->w_button_unmap->signal_clicked().connect([this] { + auto sel = win->w_tv_pads->get_selection(); + for(auto &path: sel->get_selected_rows()) { + auto it = pad_store->get_iter(path); + Gtk::TreeModel::Row row = *it; + row[pad_list_columns.gate_name] = ""; + row[pad_list_columns.pin_name] = ""; + row[pad_list_columns.pin_uuid] = horizon::UUID(); + row[pad_list_columns.gate_uuid] = horizon::UUID(); + } + update_mapped(); + }); + win->w_button_map->signal_clicked().connect([this] { + auto pin_sel = win->w_tv_pins->get_selection(); + auto it_pin = pin_sel->get_selected(); + if(it_pin) { + Gtk::TreeModel::Row row_pin = *it_pin; + std::cout << "map" << std::endl; + auto sel = win->w_tv_pads->get_selection(); + for(auto &path: sel->get_selected_rows()) { + auto it = pad_store->get_iter(path); + Gtk::TreeModel::Row row = *it; + row[pad_list_columns.gate_name] = row_pin.get_value(pin_list_columns.gate_name); + row[pad_list_columns.pin_name] = row_pin.get_value(pin_list_columns.pin_name); + row[pad_list_columns.pin_uuid] = row_pin.get_value(pin_list_columns.pin_uuid); + row[pad_list_columns.gate_uuid] = row_pin.get_value(pin_list_columns.gate_uuid); + } + if(++it_pin) { + pin_sel->select(it_pin); + } + if(sel->count_selected_rows() == 1) { + + auto it_pad = pad_store->get_iter(sel->get_selected_rows().at(0)); + sel->unselect(it_pad); + if(++it_pad) { + sel->select(it_pad); + } + } + update_mapped(); + } + }); + + entity_button->property_selected_uuid().signal_changed().connect([this]{ + horizon::UUID uu = entity_button->property_selected_uuid(); + if(uu != (part.entity?part.entity->uuid:horizon::UUID::random())) { + part.entity = pool.get_entity(uu); + part.pad_map.clear(); + update_treeview(); + } + }); + package_button->property_selected_uuid().signal_changed().connect([this]{ + horizon::UUID uu = package_button->property_selected_uuid(); + if(uu != (part.package?part.package->uuid:horizon::UUID::random())) { + part.package = pool.get_package(uu); + part.pad_map.clear(); + update_treeview(); + } + }); + part_button->property_selected_uuid().signal_changed().connect([this]{ + horizon::UUID uu = part_button->property_selected_uuid(); + if(uu == part.uuid) { + part_button->property_selected_uuid() = horizon::UUID(); + return; + } + horizon::UUID base_uu; + if(part.base) { + base_uu = part.base->uuid; + } + if(uu != base_uu) { + if(uu) { + part.base = pool.get_part(uu); + part.entity = part.base->entity; + part.package = part.base->package; + entity_button->property_selected_uuid() = part.entity->uuid; + package_button->property_selected_uuid() = part.package->uuid; + for(auto &it: win->attr_editors) { + it.second->property_can_inherit() = true; + load_entry_base(it.second, &part, it.first); + } + part.pad_map = part.base->pad_map; + } + else { + part.base = nullptr; + part.inherit_tags = false; + for(auto &it: win->attr_editors) { + it.second->property_inherit() = false; + it.second->property_can_inherit() = false; + } + part.pad_map.clear(); + } + update_tags_inherit(); + win->w_parametric_from_base->set_sensitive(part.base); + package_button->set_sensitive(!part.base); + entity_button->set_sensitive(!part.base); + win->w_button_map->set_sensitive(!part.base); + win->w_button_unmap->set_sensitive(!part.base); + update_treeview(); + part.pad_map.clear(); + + } + }); + + win->w_parametric_from_base->signal_clicked().connect([this] { + if(part.base) { + win->w_parametric->get_buffer()->set_text(serialize_parametric(part.base->parametric)); + } + }); + + package_button->set_sensitive(!part.base); + entity_button->set_sensitive(!part.base); + win->w_button_map->set_sensitive(!part.base); + win->w_button_unmap->set_sensitive(!part.base); + + + app->run(*win); + +} + +void PartEditor::save() { + for(auto &it: win->attr_editors) { + part.attributes[it.first] = it.second->get_as_pair(); + } + //part.attributes[horizon::Part::Attribute::MPN] = {false, win->w_mpn->get_text()}; + //part.value_raw = win->w_value->get_text(); + //part.manufacturer_raw = win->w_manufacturer->get_text(); + { + std::stringstream ss(win->w_tags->get_text()); + std::istream_iterator begin(ss); + std::istream_iterator end; + std::vector tags(begin, end); + part.tags.clear(); + part.tags.insert(tags.begin(), tags.end()); + } + part.inherit_tags = part.base&&win->w_tags_inherit->get_active(); + + part.parametric.clear(); + auto para = YAML::Load(win->w_parametric->get_buffer()->get_text()); + for(const auto &it: para) { + part.parametric.emplace(it.first.as(), it.second.as()); + } + + part.pad_map.clear(); + for(const auto &it: pad_store->children()) { + if(it[pad_list_columns.gate_uuid] != horizon::UUID()) { + horizon::Gate *gate = &part.entity->gates.at(it[pad_list_columns.gate_uuid]); + horizon::Pin *pin = &gate->unit->pins.at(it[pad_list_columns.pin_uuid]); + part.pad_map.emplace(it[pad_list_columns.pad_uuid], horizon::Part::PadMapItem(gate, pin)); + } + } + horizon::save_json_to_file(filename, part.serialize()); +} diff --git a/pool-util/part-editor.hpp b/pool-util/part-editor.hpp new file mode 100644 index 000000000..21895ccff --- /dev/null +++ b/pool-util/part-editor.hpp @@ -0,0 +1,65 @@ +#pragma once +#include "pool.hpp" +#include +#include "part.hpp" + +class PartEditor { + public: + PartEditor(const std::string &fname, bool create=false); + + void run(); + + private: + std::string filename; + horizon::Pool pool; + horizon::Part part; + class PartEditorWindow *win = nullptr; + class EntityButton *entity_button; + class PackageButton *package_button; + class PartButton *part_button; + void update_mapped(); + void update_treeview(); + void update_tags_inherit(); + void save(); + + + class PinListColumns : public Gtk::TreeModelColumnRecord { + public: + PinListColumns() { + Gtk::TreeModelColumnRecord::add( gate_name ) ; + Gtk::TreeModelColumnRecord::add( gate_uuid ) ; + Gtk::TreeModelColumnRecord::add( pin_name ) ; + Gtk::TreeModelColumnRecord::add( pin_uuid ) ; + Gtk::TreeModelColumnRecord::add( mapped ) ; + } + Gtk::TreeModelColumn gate_name; + Gtk::TreeModelColumn pin_name; + Gtk::TreeModelColumn gate_uuid; + Gtk::TreeModelColumn pin_uuid; + Gtk::TreeModelColumn mapped; + } ; + PinListColumns pin_list_columns; + + Glib::RefPtr pin_store; + + class PadListColumns : public Gtk::TreeModelColumnRecord { + public: + PadListColumns() { + Gtk::TreeModelColumnRecord::add( pad_name ) ; + Gtk::TreeModelColumnRecord::add( pad_uuid ) ; + Gtk::TreeModelColumnRecord::add( gate_name ) ; + Gtk::TreeModelColumnRecord::add( gate_uuid ) ; + Gtk::TreeModelColumnRecord::add( pin_name ) ; + Gtk::TreeModelColumnRecord::add( pin_uuid ) ; + } + Gtk::TreeModelColumn pad_name; + Gtk::TreeModelColumn pad_uuid; + Gtk::TreeModelColumn gate_name; + Gtk::TreeModelColumn pin_name; + Gtk::TreeModelColumn gate_uuid; + Gtk::TreeModelColumn pin_uuid; + } ; + PadListColumns pad_list_columns; + + Glib::RefPtr pad_store; +}; diff --git a/pool-util/part-editor.ui b/pool-util/part-editor.ui new file mode 100644 index 000000000..e41194132 --- /dev/null +++ b/pool-util/part-editor.ui @@ -0,0 +1,503 @@ + + + + + + False + + + True + False + 8 + 8 + 8 + 8 + vertical + 8 + + + True + False + 4 + 4 + + + True + False + MPN + 1 + + + 0 + 0 + + + + + True + False + Manufacturer + 1 + + + 0 + 2 + + + + + True + False + Value + 1 + + + 0 + 1 + + + + + True + False + Entity + 1 + + + 0 + 5 + + + + + True + False + vertical + + + + + + 1 + 5 + + + + + True + False + Package + 1 + + + 0 + 6 + + + + + True + False + vertical + + + + + + 1 + 6 + + + + + True + False + Base part + 1 + + + 0 + 7 + + + + + True + False + vertical + + + + + + 1 + 7 + + + + + True + False + Tags + 1 + + + 0 + 3 + + + + + True + True + + + 1 + 3 + + + + + True + False + True + vertical + + + + + + 1 + 0 + + + + + True + False + True + vertical + + + + + + 1 + 1 + + + + + True + False + True + vertical + + + + + + 1 + 2 + + + + + True + False + 3 + + + True + False + Inherited: + + + False + True + 0 + + + + + True + False + + + True + False + True + + + True + True + 0 + + + + + Inherit + True + True + True + + + False + True + 1 + + + + + + True + True + 1 + + + + + 1 + 4 + + + + + + + + False + True + 0 + + + + + True + False + vertical + + + True + True + in + 150 + + + True + True + True + + + + + False + True + 0 + + + + + Copy from base + True + True + True + + + False + True + 1 + + + + + + False + True + 1 + + + + + True + False + 4 + + + True + False + vertical + 4 + + + True + True + never + in + + + True + True + + + browse + + + + + + + True + True + 0 + + + + + True + False + label + 0 + + + False + True + 1 + + + + + True + True + 0 + + + + + True + False + center + False + vertical + 4 + + + Map + True + True + True + + + False + True + 1 + + + + + Unmap + True + True + True + + + False + True + 2 + + + + + False + True + 1 + + + + + True + False + vertical + 4 + + + True + True + never + in + + + True + True + True + + + multiple + + + + + + + True + True + 0 + + + + + True + False + label + 0 + + + False + True + 1 + + + + + True + True + 2 + + + + + True + True + 2 + + + + + + + True + False + Horizon Part Editor + True + + + Save + True + True + True + + + + + + + + + + + + diff --git a/pool-util/util_main.cpp b/pool-util/util_main.cpp new file mode 100644 index 000000000..cc53e4b4a --- /dev/null +++ b/pool-util/util_main.cpp @@ -0,0 +1,149 @@ +#include "uuid.hpp" +#include "unit.hpp" +#include "symbol.hpp" +#include "json.hpp" +#include "lut.hpp" +#include "common.hpp" +#include "pool.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "package.hpp" +#include "part.hpp" +#include "part-editor.hpp" +#include "util.hpp" + + +YAML::Node edit_yaml(const YAML::Emitter &em) { + std::string name_used; + int fd = Glib::file_open_tmp(name_used); + write(fd, em.c_str(), em.size()); + close(fd); + + auto editor = Glib::getenv("EDITOR"); + if(editor.size() == 0) { + editor = "vim"; + } + std::vector argv = {editor, name_used}; + int exit_status = -1; + bool yaml_error = true; + YAML::Node node; + do { + auto t_begin = Glib::DateTime::create_now_utc(); + Glib::spawn_sync("", argv, Glib::SPAWN_SEARCH_PATH|Glib::SPAWN_CHILD_INHERITS_STDIN , sigc::slot(), nullptr, nullptr, &exit_status); + auto t_end = Glib::DateTime::create_now_utc(); + auto tdelta = t_end.to_unix() -t_begin.to_unix(); + if(tdelta<2) { + std::cout << "Editor exited to soon, press return to continue;" << std::endl; + getchar(); + } + try { + node = YAML::LoadFile(name_used); + yaml_error = false; + } + catch(const std::exception& e) { + std::cout << "YAML error " << e.what() << std::endl; + getchar(); + yaml_error = true; + } + } while(yaml_error); + g_unlink(name_used.c_str()); + + + return node; +} + + +int main(int c_argc, char *c_argv[]) { + Gio::init(); + + std::vector argv; + for(int i = 0; i(), node); + auto j = unit_new.serialize(); + horizon::save_json_to_file(filename, j); + } + else if(argv.at(1) == "create-symbol") { + const auto &filename_sym = argv.at(2); + const auto &filename_unit = argv.at(3); + + horizon::Symbol sym(horizon::UUID::random()); + auto unit = horizon::Unit::new_from_file(filename_unit); + sym.unit = &unit; + horizon::save_json_to_file(filename_sym, sym.serialize()); + } + + else if(argv.at(1) == "create-entity" || argv.at(1) == "edit-entity") { + const auto &filename = argv.at(2); + horizon::Entity entity(horizon::UUID::random()); + horizon::Pool pool(pool_base_path); + if(argv.at(1) == "edit-entity") { + entity = horizon::Entity::new_from_file(filename, pool); + } + else { + if(argv.size()>3) { + for(auto it = argv.cbegin()+3; itsecond; + gate.unit = pool.get_unit(unit.uuid); + gate.name = "Main"; + } + } + } + YAML::Emitter em; + entity.serialize_yaml(em); + auto node = edit_yaml(em); + horizon::Entity entity_new (node["uuid"].as(), node, pool); + auto j = entity_new.serialize(); + horizon::save_json_to_file(filename, j); + } + + else if(argv.at(1) == "edit-part" || argv.at(1) == "create-part") { + const auto &filename = argv.at(2); + PartEditor pe(filename, argv.at(1) == "create-part"); + pe.run(); + } + + else if(argv.at(1) == "create-package") { + auto &base_path = argv.at(2); + { + auto fi = Gio::File::create_for_path(Glib::build_filename(base_path, "padstacks")); + fi->make_directory_with_parents(); + } + auto pkg_filename = Glib::build_filename(base_path, "package.json"); + horizon::Package pkg(horizon::UUID::random()); + auto j = pkg.serialize(); + horizon::save_json_to_file(pkg_filename, j); + } + + else if(argv.at(1) == "create-padstack") { + horizon::Padstack ps(horizon::UUID::random()); + auto j = ps.serialize(); + horizon::save_json_to_file(argv.at(2), j); + } + + + + return 0; +} diff --git a/pool/entity.cpp b/pool/entity.cpp new file mode 100644 index 000000000..5238af895 --- /dev/null +++ b/pool/entity.cpp @@ -0,0 +1,78 @@ +#include "entity.hpp" +#include "json.hpp" + +namespace horizon { + + Entity::Entity(const UUID &uu, const json &j, Object &obj): + uuid(uu), + name(j.at("name").get()), + prefix(j.at("prefix").get()) + + { + { + const json &o = j["gates"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + gates.emplace(std::make_pair(u, Gate(u, it.value(), obj))); + } + } + if(j.count("tags")) { + tags = j.at("tags").get>(); + } + } + + Entity::Entity(const UUID &uu): uuid(uu) {} + + Entity::Entity(const UUID &uu, const YAML::Node &n, Object &obj) : + uuid(uu), + name(n["name"].as()), + prefix(n["prefix"].as()) + { + auto tv = n["tags"].as>(std::vector()); + tags.insert(tv.begin(), tv.end()); + for(const auto &it: n["gates"]) { + UUID g_uuid = it["uuid"].as(UUID::random()); + gates.insert(std::make_pair(g_uuid, Gate(g_uuid, it, obj))); + } + } + + Entity Entity::new_from_file(const std::string &filename, Object &obj) { + json j; + std::ifstream ifs(filename); + if(!ifs.is_open()) { + throw std::runtime_error("file " +filename+ " not opened"); + } + ifs>>j; + ifs.close(); + return Entity(UUID(j["uuid"].get()), j, obj); + } + + json Entity::serialize() const { + json j; + j["type"] = "entity"; + j["name"] = name; + j["uuid"] = (std::string)uuid; + j["prefix"] = prefix; + j["tags"] = tags; + j["gates"] = json::object(); + for(const auto &it: gates) { + j["gates"][(std::string)it.first] = it.second.serialize(); + } + return j; + } + + void Entity::serialize_yaml(YAML::Emitter &em) const { + using namespace YAML; + em << BeginMap; + em << Key << "name" << Value << name; + em << Key << "uuid" << Value << (std::string)uuid; + em << Key << "prefix" << Value << prefix; + em << Key << "tags" << Value << tags; + em << Key << "gates" << Value << BeginSeq; + for(const auto &it: gates) { + it.second.serialize_yaml(em); + } + em << EndSeq; + em << EndMap; + } +} diff --git a/pool/entity.hpp b/pool/entity.hpp new file mode 100644 index 000000000..b13f64851 --- /dev/null +++ b/pool/entity.hpp @@ -0,0 +1,32 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "object.hpp" +#include "unit.hpp" +#include "gate.hpp" +#include +#include +#include + +namespace horizon { + using json = nlohmann::json; + + class Entity { + private : + Entity(const UUID &uu, const json &, Object &objj); + + public : + Entity(const UUID &uu); + Entity(const UUID &uu, const YAML::Node &n, Object &objj); + + static Entity new_from_file(const std::string &filename, Object &obj); + UUID uuid; + std::string name; + std::string prefix; + std::set tags; + std::map gates; + void serialize_yaml(YAML::Emitter &em) const; + json serialize() const; + }; + +} diff --git a/pool/gate.cpp b/pool/gate.cpp new file mode 100644 index 000000000..c44c3da1c --- /dev/null +++ b/pool/gate.cpp @@ -0,0 +1,50 @@ +#include "gate.hpp" +#include "json.hpp" + +namespace horizon { + + Gate::Gate(const UUID &uu, const json &j, Object &obj): + uuid(uu), + name(j.at("name").get()), + suffix(j.at("suffix").get()), + swap_group(j.value("swap_group", 0)), + unit(obj.get_unit(j.at("unit").get())) + + { + } + + Gate::Gate(const UUID &uu): uuid(uu) {} + + Gate::Gate(const UUID &uu, const YAML::Node &n, Object &obj) : + uuid(uu), + name(n["name"].as()), + suffix(n["suffix"].as(name)), + swap_group(n["swap_group"].as(0)), + unit(obj.get_unit(n["unit"].as())) + {} + + UUID Gate::get_uuid() const { + return uuid; + } + + json Gate::serialize() const { + json j; + j["name"] = name; + j["suffix"] = suffix; + j["swap_group"] = swap_group; + j["unit"] = (std::string)unit->uuid; + return j; + } + + void Gate::serialize_yaml(YAML::Emitter &em) const { + using namespace YAML; + em << BeginMap; + em << Key << "name" << Value << name; + em << Key << "suffix" << Value << suffix; + em << Key << "uuid" << Value << (std::string)uuid; + em << Key << "unit" << Value << (std::string)unit->uuid; + em << Key << "unit_name" << Value << unit->name; + em << Key << "swap_group" << Value << swap_group; + em << EndMap; + } +} diff --git a/pool/gate.hpp b/pool/gate.hpp new file mode 100644 index 000000000..140394aeb --- /dev/null +++ b/pool/gate.hpp @@ -0,0 +1,30 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "object.hpp" +#include "unit.hpp" +#include "uuid_provider.hpp" +#include +#include +#include + +namespace horizon { + using json = nlohmann::json; + + class Gate : public UUIDProvider { + public : + Gate(const UUID &uu, const json &, Object &objj); + Gate(const UUID &uu); + Gate(const UUID &uu, const YAML::Node &n, Object &objj); + virtual UUID get_uuid()const ; + UUID uuid; + std::string name; + std::string suffix; + unsigned int swap_group = 0; + Unit *unit = nullptr; + + json serialize() const; + void serialize_yaml(YAML::Emitter &em) const; + }; + +} diff --git a/pool/package.cpp b/pool/package.cpp new file mode 100644 index 000000000..b617d3bf3 --- /dev/null +++ b/pool/package.cpp @@ -0,0 +1,170 @@ +#include "package.hpp" +#include "json.hpp" +#include "pool.hpp" + +namespace horizon { + + Package::Package(const UUID &uu, const json &j, Pool &pool): + uuid(uu), + name(j.at("name").get()) + + { + { + const json &o = j["junctions"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + junctions.emplace(std::make_pair(u, Junction(u, it.value()))); + } + } + { + const json &o = j["lines"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + lines.emplace(std::make_pair(u, Line(u, it.value(), *this))); + } + } + { + const json &o = j["arcs"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + arcs.emplace(std::make_pair(u, Arc(u, it.value(), *this))); + } + } + { + const json &o = j["texts"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + texts.emplace(std::make_pair(u, Text(u, it.value()))); + } + } + { + const json &o = j["pads"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + pads.emplace(std::make_pair(u, Pad(u, it.value(), pool))); + } + } + + if(j.count("polygons")){ + const json &o = j["polygons"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + polygons.emplace(std::make_pair(u, Polygon(u, it.value()))); + } + } + if(j.count("tags")) { + tags = j.at("tags").get>(); + } + } + + Package Package::new_from_file(const std::string &filename, Pool &pool) { + json j; + std::ifstream ifs(filename); + if(!ifs.is_open()) { + throw std::runtime_error("file " +filename+ " not opened"); + } + ifs>>j; + ifs.close(); + return Package(UUID(j["uuid"].get()), j, pool); + } + + Package::Package(const UUID &uu): uuid(uu) {} + + Junction *Package::get_junction(const UUID &uu) { + return &junctions.at(uu); + } + + Package::Package(const Package &pkg): + uuid(pkg.uuid), + name(pkg.name), + tags(pkg.tags), + junctions(pkg.junctions), + lines(pkg.lines), + arcs(pkg.arcs), + texts(pkg.texts), + pads(pkg.pads), + polygons(pkg.polygons), + warnings(pkg.warnings) + { + update_refs(); + } + + void Package::operator=(Package const &pkg) { + uuid = pkg.uuid; + name = pkg.name; + tags = pkg.tags; + junctions = pkg.junctions; + lines = pkg.lines; + arcs = pkg.arcs; + texts = pkg.texts; + pads = pkg.pads; + polygons = pkg.polygons; + warnings = pkg.warnings; + update_refs(); + } + + void Package::update_refs() { + for(auto &it: lines) { + auto &line = it.second; + line.to = &junctions.at(line.to.uuid); + line.from = &junctions.at(line.from.uuid); + } + for(auto &it: arcs) { + auto &arc = it.second; + arc.to = &junctions.at(arc.to.uuid); + arc.from = &junctions.at(arc.from.uuid); + arc.center = &junctions.at(arc.center.uuid); + } + } + + std::pair Package::get_bbox() const { + Coordi a; + Coordi b; + for(const auto &it: pads) { + auto bb_pad = it.second.padstack.get_bbox(); + bb_pad.first = it.second.placement.transform(bb_pad.first); + bb_pad.second = it.second.placement.transform(bb_pad.second); + a = Coordi::min(a, bb_pad.first); + a = Coordi::min(a, bb_pad.second); + b = Coordi::max(b, bb_pad.first); + b = Coordi::max(b, bb_pad.second); + } + return std::make_pair(a,b); + } + + + json Package::serialize() const { + json j; + j["uuid"] = (std::string)uuid; + j["type"] = "package"; + j["name"] = name; + j["tags"] = tags; + + j["junctions"] = json::object(); + for(const auto &it: junctions) { + j["junctions"][(std::string)it.first] = it.second.serialize(); + } + j["lines"] = json::object(); + for(const auto &it: lines) { + j["lines"][(std::string)it.first] = it.second.serialize(); + } + j["arcs"] = json::object(); + for(const auto &it: arcs) { + j["arcs"][(std::string)it.first] = it.second.serialize(); + } + j["texts"] = json::object(); + for(const auto &it: texts) { + j["texts"][(std::string)it.first] = it.second.serialize(); + } + j["pads"] = json::object(); + for(const auto &it: pads) { + j["pads"][(std::string)it.first] = it.second.serialize(); + } + j["polygons"] = json::object(); + for(const auto &it: polygons) { + j["polygons"][(std::string)it.first] = it.second.serialize(); + } + return j; + } + +} diff --git a/pool/package.hpp b/pool/package.hpp new file mode 100644 index 000000000..549d12c9c --- /dev/null +++ b/pool/package.hpp @@ -0,0 +1,52 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "common.hpp" +#include "object.hpp" +#include "uuid_provider.hpp" +#include "junction.hpp" +#include "line.hpp" +#include "polygon.hpp" +#include "hole.hpp" +#include "arc.hpp" +#include "text.hpp" +#include "pad.hpp" +#include "warning.hpp" +#include +#include +#include +#include + +namespace horizon { + using json = nlohmann::json; + + + class Package : public Object { + public : + Package(const UUID &uu, const json &j, class Pool &pool); + Package(const UUID &uu); + static Package new_from_file(const std::string &filename, class Pool &pool); + + json serialize() const ; + virtual Junction *get_junction(const UUID &uu); + std::pair get_bbox() const; + + Package(const Package &pkg); + void operator=(Package const &pkg); + + UUID uuid; + std::string name; + std::set tags; + + std::map junctions; + std::map lines; + std::map arcs; + std::map texts; + std::map pads; + std::map polygons; + std::vector warnings; + + private : + void update_refs(); + }; +} diff --git a/pool/padstack.cpp b/pool/padstack.cpp new file mode 100644 index 000000000..254d83309 --- /dev/null +++ b/pool/padstack.cpp @@ -0,0 +1,102 @@ +#include "padstack.hpp" +#include "json.hpp" +#include "lut.hpp" + +namespace horizon { + + const LutEnumStr Padstack::type_lut = { + {"top", Padstack::Type::TOP}, + {"bottom", Padstack::Type::BOTTOM}, + {"through", Padstack::Type::THROUGH} + }; + + Padstack::Padstack(const UUID &uu, const json &j): + uuid(uu), + name(j.at("name").get()) + + { + { + const json &o = j["polygons"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + polygons.emplace(std::make_pair(u, Polygon(u, it.value()))); + } + } + { + const json &o = j["holes"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + holes.emplace(std::make_pair(u, Hole(u, it.value()))); + } + } + if(j.count("padstack_type")) { + type = type_lut.lookup(j.at("padstack_type")); + } + } + + Padstack Padstack::new_from_file(const std::string &filename) { + json j; + std::ifstream ifs(filename); + if(!ifs.is_open()) { + throw std::runtime_error("file " +filename+ " not opened"); + } + ifs>>j; + ifs.close(); + return Padstack(UUID(j["uuid"].get()), j); + } + + Padstack::Padstack(const UUID &uu): uuid(uu) {} + + + json Padstack::serialize() const { + json j; + j["uuid"] = (std::string)uuid; + j["type"] = "padstack"; + j["name"] = name; + j["padstack_type"] = type_lut.lookup_reverse(type); + j["polygons"] = json::object(); + for(const auto &it: polygons) { + j["polygons"][(std::string)it.first] = it.second.serialize(); + } + j["holes"] = json::object(); + for(const auto &it: holes) { + j["holes"][(std::string)it.first] = it.second.serialize(); + } + return j; + } + + UUID Padstack::get_uuid() const { + return uuid; + } + + std::pair Padstack::get_bbox() const { + Coordi a; + Coordi b; + for(const auto &it: polygons) { + auto poly = it.second.remove_arcs(8); + for(const auto &v: poly.vertices) { + a = Coordi::min(a, v.position); + b = Coordi::max(b, v.position); + } + } + return std::make_pair(a,b); + } + + void Padstack::expand_inner(unsigned int n_inner) { + if(n_inner == 0) + return; + std::map new_polygons; + for(const auto &it: polygons) { + if(it.second.layer == -1) { + for(unsigned int i=0; i<(n_inner-1); i++) { + auto uu = UUID::random(); + auto &np = new_polygons.emplace(uu, it.second).first->second; + np.uuid = uu; + np.layer = -2-i; + } + } + } + polygons.insert(new_polygons.begin(), new_polygons.end()); + } + +} diff --git a/pool/padstack.hpp b/pool/padstack.hpp new file mode 100644 index 000000000..6eceaf52b --- /dev/null +++ b/pool/padstack.hpp @@ -0,0 +1,41 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "common.hpp" +#include "uuid_provider.hpp" +#include "polygon.hpp" +#include "hole.hpp" +#include "lut.hpp" +#include +#include +#include +#include + +namespace horizon { + using json = nlohmann::json; + + + class Padstack : public UUIDProvider { + public : + enum class Type {TOP, BOTTOM, THROUGH}; + static const LutEnumStr type_lut; + + Padstack(const UUID &uu, const json &j); + Padstack(const UUID &uu); + static Padstack new_from_file(const std::string &filename); + + json serialize() const ; + + UUID uuid; + std::string name; + Type type = Type::TOP; + std::map polygons; + std::map holes; + UUID get_uuid() const override; + std::pair get_bbox() const; + void expand_inner(unsigned int n_inner); + + private : + void update_refs(); + }; +} diff --git a/pool/part.cpp b/pool/part.cpp new file mode 100644 index 000000000..1aac0ce93 --- /dev/null +++ b/pool/part.cpp @@ -0,0 +1,150 @@ +#include "part.hpp" +#include "lut.hpp" +#include "json.hpp" + +namespace horizon { + Part::Part(const UUID &uu, const json &j, Pool &pool): + uuid(uu), + inherit_tags(j.value("inherit_tags", false)) + { + attributes[Attribute::MPN] = {j.at("MPN").at(0),j.at("MPN").at(1)}; + attributes[Attribute::VALUE] = {j.at("value").at(0),j.at("value").at(1)}; + attributes[Attribute::MANUFACTURER] = {j.at("manufacturer").at(0),j.at("manufacturer").at(1)}; + if(j.count("base")) { + base = pool.get_part(j.at("base").get()); + entity = base->entity; + package = base->package; + pad_map = base->pad_map; + /*if(MPN_raw == "$") { + MPN = base->MPN; + } + if(value_raw == "$") { + value = base->value; + } + if(manufacturer_raw == "$") { + manufacturer = base->manufacturer; + }*/ + } + else { + entity = pool.get_entity(j.at("entity").get()); + package = pool.get_package(j.at("package").get()); + const json &o = j["pad_map"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto pad_uuid = UUID(it.key()); + if(package->pads.count(pad_uuid)) { + auto gate = &entity->gates.at(it->at("gate").get()); + auto pin = &gate->unit->pins.at(it->at("pin").get()); + + pad_map.insert(std::make_pair(pad_uuid, PadMapItem(gate, pin))); + } + } + } + if(j.count("tags")) { + tags = j.at("tags").get>(); + } + if(j.count("parametric")) { + const json &o = j["parametric"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + parametric.emplace(it.key(), it->get()); + } + } + //if(value.size() == 0) { + // value = MPN; + //} + } + + const std::string &Part::get_attribute(Attribute a) const { + if(attributes.count(a)) { + const auto &x = attributes.at(a); + if(x.first && base) { + return base->get_attribute(a); + } + else { + return x.second; + } + } + else { + return empty; + } + } + + const std::string &Part::get_MPN() const { + return get_attribute(Attribute::MPN); + } + + const std::string &Part::get_value() const { + const auto &r = get_attribute(Attribute::VALUE); + if(r.size() == 0) + return get_MPN(); + else + return r; + } + const std::string &Part::get_manufacturer() const { + return get_attribute(Attribute::MANUFACTURER); + } + + std::set Part::get_tags() const { + auto r = tags; + if(inherit_tags && base) { + r.insert(base->tags.begin(), base->tags.end()); + } + return r; + } + + + Part::Part(const UUID &uu): uuid(uu) { + attributes[Attribute::MPN]; + attributes[Attribute::MANUFACTURER]; + attributes[Attribute::VALUE]; + } + + Part Part::new_from_file(const std::string &filename, Pool &pool) { + json j; + std::ifstream ifs(filename); + if(!ifs.is_open()) { + throw std::runtime_error("file " +filename+ " not opened"); + } + ifs>>j; + ifs.close(); + return Part(UUID(j["uuid"].get()), j, pool); + } + + json Part::serialize() const { + json j; + j["type"] = "part"; + j["uuid"] = (std::string)uuid; + + { + const auto &a = attributes.at(Attribute::MPN); + j["MPN"] = {a.first, a.second}; + } + { + const auto &a = attributes.at(Attribute::VALUE); + j["value"] = {a.first, a.second}; + } + { + const auto &a = attributes.at(Attribute::MANUFACTURER); + j["manufacturer"] = {a.first, a.second}; + } + j["tags"] = tags; + j["inherit_tags"] = inherit_tags; + j["parametric"] = parametric; + if(base) { + j["base"] = (std::string)base->uuid; + } + else { + j["entity"] = (std::string)entity->uuid; + j["package"] = (std::string)package->uuid; + j["pad_map"] = json::object(); + for(const auto &it: pad_map) { + json k; + k["gate"] = (std::string)it.second.gate->uuid; + k["pin"] = (std::string)it.second.pin->uuid; + j["pad_map"][(std::string)it.first] = k; + } + } + + return j; + } + +} diff --git a/pool/part.hpp b/pool/part.hpp new file mode 100644 index 000000000..390c6758a --- /dev/null +++ b/pool/part.hpp @@ -0,0 +1,53 @@ +#pragma once +#include "uuid.hpp" +#include "json.hpp" +#include "package.hpp" +#include "entity.hpp" +#include +#include +#include + +namespace horizon { + using json = nlohmann::json; + + class Part { + private : + Part(const UUID &uu, const json &j, Pool &pool); + const std::string empty; + + public : + class PadMapItem { + public: + PadMapItem(Gate *g, Pin *p): gate(g), pin(p) {} + Gate *gate; + Pin *pin; + }; + Part(const UUID &uu); + + static Part new_from_file(const std::string &filename, Pool &pool); + UUID uuid; + + + enum class Attribute {MPN, VALUE, MANUFACTURER}; + std::map> attributes; + const std::string &get_attribute(Attribute a) const; + const std::pair &get_attribute_pair(Attribute a) const; + + const std::string &get_MPN() const; + const std::string &get_value() const; + const std::string &get_manufacturer() const; + std::set get_tags() const; + + std::set tags; + bool inherit_tags = false; + Entity *entity = nullptr; + Package *package = nullptr; + class Part *base = nullptr; + + std::map parametric; + + std::map pad_map; + json serialize() const; + }; + +} diff --git a/pool/pool.cpp b/pool/pool.cpp new file mode 100644 index 000000000..567b3308e --- /dev/null +++ b/pool/pool.cpp @@ -0,0 +1,85 @@ +#include "pool.hpp" +#include "json.hpp" +#include "package.hpp" +#include "part.hpp" + +namespace horizon { + Pool::Pool(const std::string &bp) :db(bp+"/pool.db", SQLITE_OPEN_READONLY), base_path(bp) { + } + + void Pool::clear() { + units.clear(); + } + + Unit *Pool::get_unit(const UUID &uu) { + if(units.count(uu) == 0) { + SQLite::Query q(db, "SELECT filename FROM units WHERE uuid = ?"); + q.bind(1, uu); + if(!q.step()) { + throw std::runtime_error("unit not found"); + } + std::string path = base_path+"/units/"+q.get(0); + Unit u = Unit::new_from_file(path); + units.insert(std::make_pair(uu, u)); + } + return &units.at(uu); + } + + Entity *Pool::get_entity(const UUID &uu) { + if(entities.count(uu) == 0) { + SQLite::Query q(db, "SELECT filename FROM entities WHERE uuid = ?"); + q.bind(1, uu); + q.step(); + std::string path = base_path+"/entities/"+q.get(0); + Entity e = Entity::new_from_file(path, *this); + entities.insert(std::make_pair(uu, e)); + } + return &entities.at(uu); + } + Symbol *Pool::get_symbol(const UUID &uu) { + if(symbols.count(uu) == 0) { + SQLite::Query q(db, "SELECT filename FROM symbols WHERE uuid = ?"); + q.bind(1, uu); + q.step(); + std::string path = base_path+"/symbols/"+q.get(0); + Symbol s = Symbol::new_from_file(path, *this); + symbols.insert(std::make_pair(uu, s)); + } + return &symbols.at(uu); + } + Package *Pool::get_package(const UUID &uu) { + if(packages.count(uu) == 0) { + SQLite::Query q(db, "SELECT filename FROM packages WHERE uuid = ?"); + q.bind(1, uu); + q.step(); + std::string path = base_path+"/packages/"+q.get(0); + Package p = Package::new_from_file(path, *this); + packages.emplace(uu, p); + } + return &packages.at(uu); + } + Padstack *Pool::get_padstack(const UUID &uu) { + if(padstacks.count(uu) == 0) { + SQLite::Query q(db, "SELECT filename FROM padstacks WHERE uuid = ?"); + q.bind(1, uu); + q.step(); + std::string path = base_path+"/packages/"+q.get(0); + Padstack p = Padstack::new_from_file(path); + padstacks.insert(std::make_pair(uu, p)); + } + return &padstacks.at(uu); + } + Part *Pool::get_part(const UUID &uu) { + if(parts.count(uu) == 0) { + SQLite::Query q(db, "SELECT filename FROM parts WHERE uuid = ?"); + q.bind(1, uu); + q.step(); + std::string path = base_path+"/parts/"+q.get(0); + Part p = Part::new_from_file(path, *this); + parts.insert(std::make_pair(uu, p)); + } + return &parts.at(uu); + } + + +} diff --git a/pool/pool.hpp b/pool/pool.hpp new file mode 100644 index 000000000..a134fc5a5 --- /dev/null +++ b/pool/pool.hpp @@ -0,0 +1,43 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "common.hpp" +#include "unit.hpp" +#include "entity.hpp" +#include "object.hpp" +#include "symbol.hpp" +#include "padstack.hpp" +#include +#include +#include +#include "sqlite.hpp" + +namespace horizon { + + class Pool : public Object { + public : + Pool(const std::string &bp); + virtual Unit *get_unit(const UUID &uu); + virtual Entity *get_entity(const UUID &uu); + virtual Symbol *get_symbol(const UUID &uu); + virtual Padstack *get_padstack(const UUID &uu); + virtual class Package *get_package(const UUID &uu); + virtual class Part *get_part(const UUID &uu); + SQLite::Database db; + void clear(); + + + private : + std::string base_path; + + std::map units; + std::map entities; + std::map symbols; + std::map padstacks; + std::map packages; + std::map parts; + + + }; + +} diff --git a/pool/symbol.cpp b/pool/symbol.cpp new file mode 100644 index 000000000..c070c1694 --- /dev/null +++ b/pool/symbol.cpp @@ -0,0 +1,212 @@ +#include "symbol.hpp" +#include "junction.hpp" +#include "line.hpp" +#include "lut.hpp" +#include "json.hpp" +#include +#include + +namespace horizon { + + static const LutEnumStr orientation_lut = { + {"up", Orientation::UP}, + {"down", Orientation::DOWN}, + {"left", Orientation::LEFT}, + {"right", Orientation::RIGHT}, + }; + + SymbolPin::SymbolPin(const UUID &uu, const json &j): + uuid(uu), + position(j["position"].get>()), + length(j["length"]), + name_visible(j.value("name_visible", true)), + pad_visible(j.value("pad_visible", true)), + orientation(orientation_lut.lookup(j["orientation"])) + { + } + + SymbolPin::SymbolPin(UUID uu): uuid(uu), name_visible(true), pad_visible(true) {} + + json SymbolPin::serialize() const { + json j; + j["position"] = position.as_array(); + j["length"] = length; + j["orientation"] = orientation_lut.lookup_reverse(orientation); + j["name_visible"] = name_visible; + j["pad_visible"] = pad_visible; + return j; + } + + UUID SymbolPin::get_uuid() const { + return uuid; + } + + Symbol::Symbol(const UUID &uu, const json &j, Object &obj): + uuid(uu), + unit(obj.get_unit(j.at("unit").get())), + name(j.value("name", "")) + { + if(j.count("junctions")) { + const json &o = j["junctions"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + junctions.emplace(std::make_pair(u, Junction(u, it.value()))); + } + } + if(j.count("lines")) { + const json &o = j["lines"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + lines.emplace(std::make_pair(u, Line(u, it.value(), *this))); + } + } + if(j.count("pins")) { + const json &o = j["pins"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + pins.emplace(std::make_pair(u, SymbolPin(u, it.value()))); + } + } + if(j.count("arcs")) { + const json &o = j["arcs"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + arcs.emplace(std::make_pair(u, Arc(u, it.value(), *this))); + } + } + if(j.count("texts")) { + const json &o = j["texts"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + texts.emplace(std::make_pair(u, Text(u, it.value()))); + } + } + } + + Symbol::Symbol(const UUID &uu): uuid(uu) {} + + Symbol Symbol::new_from_file(const std::string &filename, Object &obj) { + json j; + std::ifstream ifs(filename); + if(!ifs.is_open()) { + throw std::runtime_error("file " +filename+ " not opened"); + } + ifs>>j; + ifs.close(); + return Symbol(UUID(j["uuid"].get()), j, obj); + } + + Junction *Symbol::get_junction(const UUID &uu) { + return junctions.count(uu)?&junctions.at(uu):nullptr; + } + SymbolPin *Symbol::get_symbol_pin(const UUID &uu) { + return pins.count(uu)?&pins.at(uu):nullptr; + } + + void Symbol::update_refs() { + for(auto &it: lines) { + auto &line = it.second; + line.to = &junctions.at(line.to.uuid); + line.from = &junctions.at(line.from.uuid); + } + for(auto &it: arcs) { + auto &arc = it.second; + arc.to = &junctions.at(arc.to.uuid); + arc.from = &junctions.at(arc.from.uuid); + arc.center = &junctions.at(arc.center.uuid); + } + + } + + Symbol::Symbol(const Symbol &sym): + uuid(sym.uuid), + unit(sym.unit), + name(sym.name), + pins(sym.pins), + junctions(sym.junctions), + lines(sym.lines), + arcs(sym.arcs), + texts(sym.texts) + { + update_refs(); + } + + void Symbol::operator=(Symbol const &sym) { + uuid = sym.uuid; + unit = sym.unit; + name = sym.name; + pins = sym.pins; + junctions = sym.junctions; + lines = sym.lines; + arcs = sym.arcs; + texts = sym.texts; + update_refs(); + } + + void Symbol::expand() { + std::vector keys; + keys.reserve(pins.size()); + for(const auto &it: pins) { + keys.push_back(it.first); + } + for(const auto &uu: keys) { + if(unit->pins.count(uu)) { + SymbolPin &p = pins.at(uu); + p.pad = "$PAD"; + p.name = unit->pins.at(uu).primary_name; + } + else { + pins.erase(uu); + } + } + } + + std::pair Symbol::get_bbox(bool all) const { + Coordi a; + Coordi b; + for(const auto &it: junctions) { + a = Coordi::min(a, it.second.position); + b = Coordi::max(b, it.second.position); + } + for(const auto &it: pins) { + a = Coordi::min(a, it.second.position); + b = Coordi::max(b, it.second.position); + } + if(all) { + for(const auto &it: texts) { + a = Coordi::min(a, it.second.position); + b = Coordi::max(b, it.second.position); + } + } + return std::make_pair(a,b); + } + + json Symbol::serialize() const { + json j; + j["type"] = "symbol"; + j["name"] = name; + j["uuid"] = (std::string)uuid; + j["unit"] = (std::string)unit->uuid; + j["junctions"] = json::object(); + for(const auto &it: junctions) { + j["junctions"][(std::string)it.first] = it.second.serialize(); + } + j["pins"] = json::object(); + for(const auto &it: pins) { + j["pins"][(std::string)it.first] = it.second.serialize(); + } + j["lines"] = json::object(); + for(const auto &it: lines) { + j["lines"][(std::string)it.first] = it.second.serialize(); + } + j["arcs"] = json::object(); + for(const auto &it: arcs) { + j["arcs"][(std::string)it.first] = it.second.serialize(); + } + j["texts"] = json::object(); + for(const auto &it: texts) { + j["texts"][(std::string)it.first] = it.second.serialize(); + } + return j; + } +} diff --git a/pool/symbol.hpp b/pool/symbol.hpp new file mode 100644 index 000000000..30986bd5c --- /dev/null +++ b/pool/symbol.hpp @@ -0,0 +1,74 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "common.hpp" +#include "unit.hpp" +#include "junction.hpp" +#include "line.hpp" +#include "arc.hpp" +#include "object.hpp" +#include "text.hpp" +#include "uuid_provider.hpp" +#include +#include +#include +#include + +namespace horizon { + using json = nlohmann::json; + + + class SymbolPin: public UUIDProvider { + public : + enum class ConnectorStyle {BOX, NONE}; + + SymbolPin(const UUID &uu, const json &j); + SymbolPin(UUID uu); + + const UUID uuid; + Coord position; + uint64_t length; + bool name_visible; + bool pad_visible; + Orientation orientation; + + //not stored + std::string name; + std::string pad; + ConnectorStyle connector_style = ConnectorStyle::BOX; + std::map connected_net_lines; + UUID net_segment; + + + json serialize() const; + virtual UUID get_uuid() const; + }; + + class Symbol : public Object { + public : + Symbol(const UUID &uu, const json &j, Object &obj); + Symbol(const UUID &uu); + static Symbol new_from_file(const std::string &filename, Object &obj); + std::pair get_bbox(bool all=false) const; + virtual Junction *get_junction(const UUID &uu); + virtual SymbolPin *get_symbol_pin(const UUID &uu); + + json serialize() const ; + + void expand(); + Symbol(const Symbol &sym); + void operator=(Symbol const &sym); + + UUID uuid; + Unit *unit; + std::string name; + std::map pins; + std::map junctions; + std::map lines; + std::map arcs; + std::map texts; + + private : + void update_refs(); + }; +} diff --git a/pool/unit.cpp b/pool/unit.cpp new file mode 100644 index 000000000..aa4145d4a --- /dev/null +++ b/pool/unit.cpp @@ -0,0 +1,118 @@ +#include "unit.hpp" +#include "lut.hpp" +#include "json.hpp" + +namespace horizon { + static const LutEnumStr pin_direction_lut = { + {"output", Pin::Direction::OUTPUT}, + {"input", Pin::Direction::INPUT}, + {"power_input", Pin::Direction::POWER_INPUT}, + {"passive", Pin::Direction::PASSIVE}, + }; + + Pin::Pin(const UUID &uu, const json &j): + uuid(uu), + primary_name(j.at("primary_name").get()), + direction(pin_direction_lut.lookup(j.at("direction"))), + swap_group(j.value("swap_group", 0)), + names(j.value("names", std::vector())) + { + } + + Pin::Pin(const UUID &uu, const YAML::Node &n) : + uuid(uu), + primary_name(n["primary_name"].as()), + direction(pin_direction_lut.lookup(n["direction"].as("input"))), + swap_group(n["swap_group"].as(0)), + names(n["names"].as>(std::vector())) + {} + + Pin::Pin(const UUID &uu): uuid(uu) {} + + + json Pin::serialize() const { + json j; + j["primary_name"] = primary_name; + j["direction"] = pin_direction_lut.lookup_reverse(direction); + j["swap_group"] = swap_group; + j["names"] = names; + return j; + } + + void Pin::serialize_yaml(YAML::Emitter &em) const { + using namespace YAML; + em << BeginMap; + em << Key << "primary_name" << Value << primary_name; + em << Key << "uuid" << Value << (std::string)uuid; + em << Key << "names" << Value << names; + em << Key << "direction" << Value << pin_direction_lut.lookup_reverse(direction); + em << Key << "swap_group" << Value << swap_group; + em << EndMap; + /*YAML::Node n; + n["uuid"] = (std::string)uuid; + n["primary_name"] = primary_name; + n["names"] = names; + n["direction"] = pin_direction_lut.lookup_reverse(direction); + return n;*/ + } + + Unit::Unit(const UUID &uu, const json &j): + uuid(uu), + name(j["name"].get()) + { + const json &o = j["pins"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto pin_uuid = UUID(it.key()); + pins.insert(std::make_pair(pin_uuid, Pin(pin_uuid, it.value()))); + } + } + + Unit::Unit(const UUID &uu, const YAML::Node &n) : + uuid(uu), + name(n["name"].as()) + { + for(const auto &it: n["pins"]) { + UUID pin_uuid = it["uuid"].as(UUID::random()); + pins.insert(std::make_pair(pin_uuid, Pin(pin_uuid, it))); + } + } + + Unit::Unit(const UUID &uu):uuid(uu) {} + + Unit Unit::new_from_file(const std::string &filename) { + json j; + std::ifstream ifs(filename); + if(!ifs.is_open()) { + throw std::runtime_error("file " +filename+ " not opened"); + } + ifs>>j; + ifs.close(); + return Unit(UUID(j["uuid"].get()), j); + } + + json Unit::serialize() const { + json j; + j["type"] = "unit"; + j["name"] = name; + j["uuid"] = (std::string)uuid; + j["pins"] = json::object(); + for(const auto &it: pins) { + j["pins"][(std::string)it.first] = it.second.serialize(); + } + return j; + } + + void Unit::serialize_yaml(YAML::Emitter &em) const { + using namespace YAML; + em << BeginMap; + em << Key << "name" << Value << name; + em << Key << "uuid" << Value << (std::string)uuid; + em << Key << "pins" << Value << BeginSeq; + for(const auto &it: pins) { + it.second.serialize_yaml(em); + } + em << EndSeq; + em << EndMap; + } + +} diff --git a/pool/unit.hpp b/pool/unit.hpp new file mode 100644 index 000000000..aa9bfcb22 --- /dev/null +++ b/pool/unit.hpp @@ -0,0 +1,48 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "object.hpp" +#include +#include +#include +#include +#include + +namespace horizon { + using json = nlohmann::json; + + + class Pin { + public : + enum class Direction {INPUT, OUTPUT, POWER_INPUT, PASSIVE}; + + Pin(const UUID &uu, const json &j); + Pin(const UUID &uu, const YAML::Node &n); + Pin(const UUID &uu); + + const UUID uuid; + std::string primary_name; + Direction direction = Direction::INPUT; + unsigned int swap_group = 0; + std::vector names; + + json serialize() const; + void serialize_yaml(YAML::Emitter &em) const; + }; + + class Unit { + private : + Unit(const UUID &uu, const json &j); + + public : + static Unit new_from_file(const std::string &filename); + Unit(const UUID &uu); + Unit(const UUID &uu, const YAML::Node &n); + UUID uuid; + std::string name; + std::map pins; + json serialize() const; + void serialize_yaml(YAML::Emitter &em) const; + }; + +} diff --git a/prj-util/util_main.cpp b/prj-util/util_main.cpp new file mode 100644 index 000000000..465c960ea --- /dev/null +++ b/prj-util/util_main.cpp @@ -0,0 +1,72 @@ +#include "uuid.hpp" +#include "unit.hpp" +#include "symbol.hpp" +#include "json.hpp" +#include "lut.hpp" +#include "common.hpp" +#include "pool.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "package.hpp" +#include "part.hpp" +#include "schematic.hpp" +#include "board.hpp" + + +using json = nlohmann::json; + +static void save_json_to_file(const std::string &filename, const json &j) { + std::ofstream ofs(filename); + if(!ofs.is_open()) { + std::cout << "can't save json " << filename < argv; + for(int i = 0; i= 4) { + block.name = argv.at(3); + } + save_json_to_file(argv.at(2), block.serialize()); + } + else if(argv.at(1) == "create-constraints") { + horizon::Constraints constraints; + save_json_to_file(argv.at(2), constraints.serialize()); + } + else if(argv.at(1) == "create-schematic") { + horizon::Pool pool(pool_base_path); + auto constraints = horizon::Constraints::new_from_file(argv.at(4)); + auto block = horizon::Block::new_from_file(argv.at(3), pool, &constraints); + horizon::Schematic sch(horizon::UUID::random(), block); + save_json_to_file(argv.at(2), sch.serialize()); + } + else if(argv.at(1) == "create-board") { + horizon::Pool pool(pool_base_path); + auto constraints = horizon::Constraints::new_from_file(argv.at(4)); + auto block = horizon::Block::new_from_file(argv.at(3), pool, &constraints); + horizon::Board brd(horizon::UUID::random(), block); + save_json_to_file(argv.at(2), brd.serialize()); + } + + return 0; +} diff --git a/property_panels/property_editor.cpp b/property_panels/property_editor.cpp new file mode 100644 index 000000000..a509f31fc --- /dev/null +++ b/property_panels/property_editor.cpp @@ -0,0 +1,240 @@ +#include "property_editor.hpp" +#include "object_descr.hpp" +#include "property_editors.hpp" +#include "property_panel.hpp" +#include "property_panels.hpp" +#include +#include +#include + +namespace horizon { + PropertyEditor::PropertyEditor(const UUID &uu, ObjectType t, ObjectProperty::ID prop, Core *c, class PropertyEditors *p): + Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL, 4), + parent(p), + property_id(prop), + core(c), + uuid(uu), + type(t), + property(object_descriptions.at(type).properties.at(property_id)) + { + auto *hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL,4)); + auto *header = Gtk::manage(new Gtk::Label()); + header->set_markup(""+property.label+""); + header->set_halign(Gtk::ALIGN_START); + hbox->pack_start(*header, true, true, 0); + + apply_all_button = Gtk::manage(new Gtk::Button()); + apply_all_button->set_image_from_icon_name("object-select-symbolic"); + apply_all_button->signal_clicked().connect(sigc::mem_fun(this, &PropertyEditor::handle_apply_all)); + hbox->pack_start(*apply_all_button, false, false, 0); + + pack_start(*hbox, false, false, 0); + } + + void PropertyEditor::handle_apply_all() { + auto x = parent->parent->stack; + for(auto it: x->get_children()) { + auto pes = dynamic_cast(it); + auto pe = pes->get_editor_for_property(property_id); + if(pe != this) { + pe->set_from_other(this); + } + } + } + + void PropertyEditor::construct() { + auto *ed = create_editor(); + ed->set_halign(Gtk::ALIGN_END); + pack_start(*ed, false, false, 0); + } + + Gtk::Widget *PropertyEditor::create_editor() { + Gtk::Label *la = Gtk::manage(new Gtk::Label("fixme")); + return la; + } + + Gtk::Widget *PropertyEditorBool::create_editor() { + sw = Gtk::manage(new Gtk::Switch()); + reload(); + sw->property_active().signal_changed().connect(sigc::mem_fun(this, &PropertyEditorBool::changed)); + return sw; + } + + void PropertyEditorBool::changed() { + bool st = sw->get_active(); + core->set_property_bool(uuid,type, property_id, st); + reload(); + parent->parent->parent->signal_update().emit(); + } + + void PropertyEditorBool::set_from_other(PropertyEditor *other) { + auto o = dynamic_cast(other); + sw->set_active(o->sw->get_active()); + } + + void PropertyEditorBool::reload() { + sw->set_active(core->get_property_bool(uuid,type, property_id)); + sw->set_sensitive(core->property_is_settable(uuid,type, property_id)); + } + + Gtk::Widget *PropertyEditorStringRO::create_editor() { + apply_all_button->set_sensitive(false); + la = Gtk::manage(new Gtk::Label(core->get_property_string(uuid,type, property_id))); + return la; + } + + void PropertyEditorStringRO::reload() { + la->set_text(core->get_property_string(uuid,type, property_id)); + } + + Gtk::Widget *PropertyEditorLength::create_editor() { + sp = Gtk::manage(new Gtk::SpinButton()); + sp->set_range(0, 1e9); + reload(); + sp->set_increments(.5e6, 10); + sp->signal_output().connect(sigc::mem_fun(this, &PropertyEditorLength::sp_output)); + sp->signal_input().connect(sigc::mem_fun(this, &PropertyEditorLength::sp_input)); + sp->signal_value_changed().connect(sigc::mem_fun(this, &PropertyEditorLength::changed)); + return sp; + } + + void PropertyEditorLength::reload() { + sp->set_value(core->get_property_int(uuid,type, property_id)); + sp->set_sensitive(core->property_is_settable(uuid,type, property_id)); + } + + bool PropertyEditorLength::sp_output() { + auto adj = sp->get_adjustment(); + double value = adj->get_value(); + + std::stringstream stream; + stream << std::fixed << std::setprecision(2) << value/1e6 << " mm"; + + sp->set_text(stream.str()); + return true; + } + + int PropertyEditorLength::sp_input(double *v) { + auto txt = sp->get_text(); + int64_t va = 0; + try { + va = std::stod(txt)*1e6; + *v = va; + } + catch (const std::exception& e) { + return false; + } + + + return true; + } + + void PropertyEditorLength::changed() { + int64_t va = sp->get_value_as_int(); + core->set_property_int(uuid,type, property_id, va); + sp->set_value(core->get_property_int(uuid,type, property_id)); + parent->parent->parent->signal_update().emit(); + } + + void PropertyEditorLength::set_from_other(PropertyEditor *other) { + auto o = dynamic_cast(other); + sp->set_value(o->sp->get_value()); + } + + Gtk::Widget *PropertyEditorString::create_editor() { + en = Gtk::manage(new Gtk::Entry()); + en->set_alignment(1); + reload(); + en->signal_changed().connect(sigc::mem_fun(this, &PropertyEditorString::changed)); + en->signal_activate().connect(sigc::mem_fun(this, &PropertyEditorString::activate)); + en->signal_focus_out_event().connect(sigc::mem_fun(this, &PropertyEditorString::focus_out_event)); + return en; + } + + void PropertyEditorString::set_from_other(PropertyEditor *other) { + auto o = dynamic_cast(other); + en->set_text(o->en->get_text()); + activate(); + } + + void PropertyEditorString::activate() { + if(!modified) + return; + modified = false; + std::string txt = en->get_text(); + core->set_property_string(uuid,type, property_id, txt); + parent->parent->parent->signal_update().emit(); + en->unset_icon(Gtk::ENTRY_ICON_PRIMARY); + } + + bool PropertyEditorString::focus_out_event(GdkEventFocus *e) { + activate(); + return false; + } + + void PropertyEditorString::reload() { + en->set_text(core->get_property_string(uuid,type, property_id)); + en->unset_icon(Gtk::ENTRY_ICON_PRIMARY); + en->set_sensitive(core->property_is_settable(uuid,type, property_id)); + modified = false; + } + + void PropertyEditorString::changed() { + en->set_icon_from_icon_name("content-loading-symbolic", Gtk::ENTRY_ICON_PRIMARY); + modified = true; + } + + Gtk::Widget *PropertyEditorLayer::create_editor() { + combo = Gtk::manage(new Gtk::ComboBoxText()); + for(const auto &it: core->get_layers()) { + if(!copper_only || it.second.copper) + combo->insert(0, std::to_string(it.first), it.second.name); + } + reload(); + combo->signal_changed().connect(sigc::mem_fun(this, &PropertyEditorLayer::changed)); + return combo; + } + + void PropertyEditorLayer::reload() { + combo->set_active_id(std::to_string(core->get_property_int(uuid, type, property_id))); + } + + void PropertyEditorLayer::changed() { + int la = std::stoi(combo->get_active_id()); + core->set_property_int(uuid, type, property_id, la); + parent->parent->parent->signal_update().emit(); + } + + void PropertyEditorLayer::set_from_other(PropertyEditor *other) { + auto o = dynamic_cast(other); + combo->set_active_id(o->combo->get_active_id()); + } + + Gtk::Widget *PropertyEditorNetClass::create_editor() { + combo = Gtk::manage(new Gtk::ComboBoxText()); + auto constraints = core->get_constraints(); + if(constraints) { + for(const auto &it: constraints->net_classes) { + combo->insert(0, (std::string)it.first, it.second.name); + } + } + reload(); + combo->signal_changed().connect(sigc::mem_fun(this, &PropertyEditorNetClass::changed)); + return combo; + } + + void PropertyEditorNetClass::reload() { + combo->set_active_id(core->get_property_string(uuid, type, property_id)); + } + + void PropertyEditorNetClass::changed() { + std::string nc = combo->get_active_id(); + core->set_property_string(uuid, type, property_id, nc); + parent->parent->parent->signal_update().emit(); + } + + void PropertyEditorNetClass::set_from_other(PropertyEditor *other) { + auto o = dynamic_cast(other); + combo->set_active_id(o->combo->get_active_id()); + } +} diff --git a/property_panels/property_editor.hpp b/property_panels/property_editor.hpp new file mode 100644 index 000000000..607d924d8 --- /dev/null +++ b/property_panels/property_editor.hpp @@ -0,0 +1,112 @@ +#pragma once +#include +#include "core/core.hpp" +#include "object_descr.hpp" + +namespace horizon { + class PropertyEditor: public Gtk::Box { + public: + PropertyEditor(const UUID &uu, ObjectType t, ObjectProperty::ID prop, Core *c, class PropertyEditors *p); + void construct(); + virtual void reload() {} + + virtual ~PropertyEditor() {} + + class PropertyEditors *parent; + const ObjectProperty::ID property_id; + protected: + Core *core; + const UUID uuid; + const ObjectType type; + + const ObjectProperty &property; + Gtk::Button *apply_all_button; + + virtual Gtk::Widget *create_editor(); + virtual void set_from_other(PropertyEditor *other) {} + + private : + void handle_apply_all(); + }; + + class PropertyEditorBool : public PropertyEditor { + using PropertyEditor::PropertyEditor; + public: + virtual void reload(); + protected: + virtual Gtk::Widget *create_editor(); + private : + Gtk::Switch *sw; + void changed(); + virtual void set_from_other(PropertyEditor *other); + }; + class PropertyEditorStringRO : public PropertyEditor { + using PropertyEditor::PropertyEditor; + public: + virtual void reload(); + protected: + virtual Gtk::Widget *create_editor(); + private : + Gtk::Label *la; + }; + + class PropertyEditorLength : public PropertyEditor { + using PropertyEditor::PropertyEditor; + protected: + virtual Gtk::Widget *create_editor(); + private : + Gtk::SpinButton *sp; + bool sp_output(); + int sp_input(double *v); + void changed(); + virtual void set_from_other(PropertyEditor *other); + virtual void reload(); + }; + + class PropertyEditorString: public PropertyEditor { + using PropertyEditor::PropertyEditor; + public: + virtual void reload(); + + protected: + virtual Gtk::Widget *create_editor(); + private : + Gtk::Entry *en; + void changed(); + void activate(); + bool focus_out_event(GdkEventFocus *e); + virtual void set_from_other(PropertyEditor *other); + bool modified = false; + }; + + class PropertyEditorLayer: public PropertyEditor { + using PropertyEditor::PropertyEditor; + public: + virtual void reload(); + bool copper_only = false; + + protected: + virtual Gtk::Widget *create_editor(); + + private : + Gtk::ComboBoxText *combo; + void changed(); + virtual void set_from_other(PropertyEditor *other); + }; + + class PropertyEditorNetClass: public PropertyEditor { + using PropertyEditor::PropertyEditor; + public: + virtual void reload(); + + protected: + virtual Gtk::Widget *create_editor(); + + private : + Gtk::ComboBoxText *combo; + void changed(); + virtual void set_from_other(PropertyEditor *other); + }; + + +} diff --git a/property_panels/property_editors.cpp b/property_panels/property_editors.cpp new file mode 100644 index 000000000..765566d46 --- /dev/null +++ b/property_panels/property_editors.cpp @@ -0,0 +1,67 @@ +#include "property_editors.hpp" +#include "property_editor.hpp" +#include "object_descr.hpp" +#include +#include + +namespace horizon { + PropertyEditors::PropertyEditors(const UUID &uu, ObjectType t, Core *c, PropertyPanel *p): + Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL, 16), + uuid(uu), + type(t), + parent(p), + core(c) + { + for(const auto &it: object_descriptions.at(type).properties) { + PropertyEditor *e; + switch(it.second.type) { + case ObjectProperty::Type::BOOL : + e = new PropertyEditorBool(uuid, type, it.first, core, this); + break; + case ObjectProperty::Type::STRING_RO: + e = new PropertyEditorStringRO(uuid, type, it.first, core, this); + break; + case ObjectProperty::Type::LENGTH: + e = new PropertyEditorLength(uuid, type, it.first, core, this); + break; + case ObjectProperty::Type::STRING: + e = new PropertyEditorString(uuid, type, it.first, core, this); + break; + case ObjectProperty::Type::LAYER: + e = new PropertyEditorLayer(uuid, type, it.first, core, this); + break; + case ObjectProperty::Type::LAYER_COPPER: { + auto pe = new PropertyEditorLayer(uuid, type, it.first, core, this); + pe->copper_only = true; + e = pe; + } break; + case ObjectProperty::Type::NET_CLASS: + e = new PropertyEditorNetClass(uuid, type, it.first, core, this); + break; + + default : + e = new PropertyEditor(uuid, type, it.first, core, this); + } + auto em = Gtk::manage(e); + em->construct(); + pack_start(*em, false, false, 0); + } + } + + PropertyEditor *PropertyEditors::get_editor_for_property(ObjectProperty::ID id) { + for(auto it: get_children()) { + auto pe = dynamic_cast(it); + if(pe->property_id == id) { + return pe; + } + } + return nullptr; + } + + void PropertyEditors::reload() { + for(auto it: get_children()) { + auto pe = dynamic_cast(it); + pe->reload(); + } + } +} diff --git a/property_panels/property_editors.hpp b/property_panels/property_editors.hpp new file mode 100644 index 000000000..b8441e949 --- /dev/null +++ b/property_panels/property_editors.hpp @@ -0,0 +1,24 @@ +#pragma once +#include +#include "core/core.hpp" +#include "common.hpp" + +namespace horizon { + class PropertyEditors: public Gtk::Box { + public: + PropertyEditors(const UUID &uu, ObjectType t, Core *c, class PropertyPanel *p); + + const UUID uuid; + const ObjectType type; + class PropertyPanel *parent; + class PropertyEditor *get_editor_for_property(enum ObjectProperty::ID id); + void reload(); + private: + Core *core; + + + + }; + + +} diff --git a/property_panels/property_panel.cpp b/property_panels/property_panel.cpp new file mode 100644 index 000000000..5729b5f10 --- /dev/null +++ b/property_panels/property_panel.cpp @@ -0,0 +1,100 @@ +#include "property_panel.hpp" +#include +#include "object_descr.hpp" +#include "property_editors.hpp" + +namespace horizon { + + PropertyPanel::PropertyPanel(BaseObjectType* cobject, const Glib::RefPtr& x) : + Gtk::Expander(cobject) { + x->get_widget("stack", stack); + x->get_widget("selector_label", selector_label); + x->get_widget("button_prev", button_prev); + x->get_widget("button_next", button_next); + + button_next->signal_clicked().connect(sigc::bind(sigc::mem_fun(this, &PropertyPanel::go), 1)); + button_prev->signal_clicked().connect(sigc::bind(sigc::mem_fun(this, &PropertyPanel::go), -1)); + } + + PropertyPanel* PropertyPanel::create(ObjectType t, Core *c, PropertyPanels *parent) { + PropertyPanel* w; + Glib::RefPtr x = Gtk::Builder::create(); + x->add_from_resource("/net/carrotIndustries/horizon/property_panels/property_panel.ui"); + x->get_widget_derived("PropertyPanel", w); + w->reference(); + w->type = t; + w->core = c; + w->parent = parent; + + w->set_use_markup(true); + w->set_label(""+object_descriptions.at(w->type).name_pl+""); + return w; + } + + void PropertyPanel::update_selector() { + auto *cur = stack->get_visible_child(); + auto children = stack->get_children(); + auto f = std::find(children.begin(), children.end(), cur); + unsigned int i = f-children.begin(); + + std::string l = std::to_string(i+1)+"/"+std::to_string(children.size()); + selector_label->set_text(l); + } + + void PropertyPanel::go(int dir) { + auto *cur = stack->get_visible_child(); + auto children = stack->get_children(); + auto f = std::find(children.begin(), children.end(), cur); + int i = f-children.begin(); + i+=dir; + if(i<0) { + i+=children.size(); + } + i %= children.size(); + stack->set_visible_child(*children.at(i)); + update_selector(); + } + + void PropertyPanel::update_objects(const std::set &selection) { + std::map editors; + std::set editors_present; + for(const auto &it: stack->get_children()) { + auto ed = dynamic_cast(it); + editors.emplace(ed->uuid, ed); + editors_present.emplace(ed->uuid); + } + std::set editors_from_selection; + for(const auto &it: selection) { + editors_from_selection.emplace(it.uuid); + } + std::set editor_create; + std::set_difference(editors_from_selection.begin(), editors_from_selection.end(), editors_present.begin(), editors_present.end(), + std::inserter(editor_create, editor_create.begin())); + std::set editor_delete; + std::set_difference(editors_present.begin(), editors_present.end(), editors_from_selection.begin(), editors_from_selection.end(), + std::inserter(editor_delete, editor_delete.begin())); + for(const auto it: editor_create) { + std::cout << "create editor " << (std::string)it << std::endl; + + auto *ed = Gtk::manage(new PropertyEditors(it, type, core, this)); + ed->show_all(); + stack->add(*ed); + } + for(const auto it: editor_delete) { + std::cout << "delete editor " << (std::string)it << std::endl; + delete editors.at(it); + } + update_selector(); + } + + ObjectType PropertyPanel::get_type() { + return type; + } + + void PropertyPanel::reload() { + for(const auto &it: stack->get_children()) { + auto ed = dynamic_cast(it); + ed->reload(); + } + } +} diff --git a/property_panels/property_panel.hpp b/property_panels/property_panel.hpp new file mode 100644 index 000000000..57946baed --- /dev/null +++ b/property_panels/property_panel.hpp @@ -0,0 +1,32 @@ +#pragma once +#include +#include "common.hpp" +#include "core/core.hpp" + +namespace horizon { + + class PropertyPanel: public Gtk::Expander { + friend class PropertyEditor; + public: + PropertyPanel(BaseObjectType* cobject, const Glib::RefPtr& x); + static PropertyPanel* create(ObjectType t, Core *c, class PropertyPanels *parent); + ObjectType get_type(); + void update_objects(const std::set &selection); + class PropertyPanels *parent; + void reload(); + + virtual ~PropertyPanel(){}; + private : + ObjectType type; + Core *core; + Gtk::Stack *stack; + Gtk::Label *selector_label; + Gtk::Button *button_prev; + Gtk::Button *button_next; + + void update_selector(); + void go(int dir); + + + }; +} diff --git a/property_panels/property_panel.ui b/property_panels/property_panel.ui new file mode 100644 index 000000000..f559becc0 --- /dev/null +++ b/property_panels/property_panel.ui @@ -0,0 +1,105 @@ + + + + + + True + True + True + 4 + + + True + False + vertical + + + True + False + 4 + + + True + True + True + image3 + none + + + True + True + 0 + + + + + True + False + m/n + + + False + True + 1 + + + + + True + True + True + image4 + none + + + True + True + 2 + + + + + False + True + 0 + + + + + True + False + 8 + 100 + slide-left-right + + + + + + True + True + 1 + + + + + + + True + False + expander + True + + + + + True + False + pan-start-symbolic + + + True + False + pan-end-symbolic + + diff --git a/property_panels/property_panels.cpp b/property_panels/property_panels.cpp new file mode 100644 index 000000000..a9f9345fa --- /dev/null +++ b/property_panels/property_panels.cpp @@ -0,0 +1,66 @@ +#include "property_panels.hpp" +#include "property_panel.hpp" +#include "object_descr.hpp" +#include +#include + +namespace horizon { + PropertyPanels::PropertyPanels(Core *c): + Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL, 16), + core(c) + { + + } + + void PropertyPanels::update_objects(const std::set &selection) { + std::set to_delete; + std::set types; + std::set types_present; + for(const auto &it: selection) { + types.emplace(it.type); + } + std::map children; + for(const auto &it: get_children()) { + auto pp = dynamic_cast(it); + types_present.emplace(pp->get_type()); + children.emplace(pp->get_type(), pp); + } + + std::set panel_create; + std::set_difference(types.begin(), types.end(), types_present.begin(), types_present.end(), + std::inserter(panel_create, panel_create.begin())); + std::set panel_delete; + std::set_difference(types_present.begin(), types_present.end(), types.begin(), types.end(), + std::inserter(panel_delete, panel_delete.begin())); + for(const auto it: panel_create) { + if(object_descriptions.count(it)) { + if(object_descriptions.at(it).properties.size()>0) { + auto *pp = PropertyPanel::create(it, core, this); + pp->show_all(); + pack_start(*pp, false, false, 0); + pp->unreference(); + } + } + } + for(const auto it: panel_delete) { + std::cout << "delete panel " << object_descriptions.at(it).name << std::endl; + delete children.at(it); + } + + for(const auto &it: get_children()) { + auto pp = dynamic_cast(it); + auto ty = pp->get_type(); + std::set sel_this; + std::copy_if(selection.begin(), selection.end(), std::inserter(sel_this, sel_this.begin()), [ty](const auto &x){return x.type==ty;}); + pp->update_objects(sel_this); + } + reload(); + } + + void PropertyPanels::reload() { + for(const auto &it: get_children()) { + auto pp = dynamic_cast(it); + pp->reload(); + } + } +} diff --git a/property_panels/property_panels.hpp b/property_panels/property_panels.hpp new file mode 100644 index 000000000..e116754cc --- /dev/null +++ b/property_panels/property_panels.hpp @@ -0,0 +1,21 @@ +#pragma once +#include +#include "core/core.hpp" + +namespace horizon { + class PropertyPanels: public Gtk::Box { + public: + PropertyPanels(Core *c); + void update_objects(const std::set &selection); + void reload(); + typedef sigc::signal type_signal_update; + type_signal_update signal_update() {return s_signal_update;} + + + private: + Core *core; + type_signal_update s_signal_update; + }; + + +} diff --git a/schematic/bus_label.cpp b/schematic/bus_label.cpp new file mode 100644 index 000000000..e7a2c3c09 --- /dev/null +++ b/schematic/bus_label.cpp @@ -0,0 +1,37 @@ +#include "bus_label.hpp" +#include "lut.hpp" +#include "sheet.hpp" +#include "block.hpp" +#include "json.hpp" + +namespace horizon { + + static const LutEnumStr orientation_lut = { + {"up", Orientation::UP}, + {"down", Orientation::DOWN}, + {"left", Orientation::LEFT}, + {"right", Orientation::RIGHT}, + }; + + BusLabel::BusLabel(const UUID &uu, const json &j, Sheet &sheet, Block &block): + uuid(uu), + junction(&sheet.junctions.at(j.at("junction").get())), + orientation(orientation_lut.lookup(j.at("orientation"))), + size(j.value("size", 2500000)), + offsheet_refs(j.value("offsheet_refs", true)), + bus(&block.buses.at(j.at("bus").get())) + { + } + + BusLabel::BusLabel(const UUID &uu): uuid(uu) {} + + json BusLabel::serialize() const { + json j; + j["junction"] = (std::string)junction->uuid; + j["orientation"] = orientation_lut.lookup_reverse(orientation); + j["size"] = size; + j["offsheet_refs"] = offsheet_refs; + j["bus"] = (std::string)bus->uuid; + return j; + } +} diff --git a/schematic/bus_label.hpp b/schematic/bus_label.hpp new file mode 100644 index 000000000..65722e7d8 --- /dev/null +++ b/schematic/bus_label.hpp @@ -0,0 +1,36 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "common.hpp" +#include "uuid_ptr.hpp" +#include "junction.hpp" +#include "bus.hpp" +#include +#include +#include +#include + + +namespace horizon { + using json = nlohmann::json; + + class BusLabel { + public : + BusLabel(const UUID &uu, const json &j, class Sheet &sheet, class Block &block); + BusLabel(const UUID &uu); + + const UUID uuid; + + enum class Style {PLAIN, FLAG}; + Style style = Style::FLAG; + + uuid_ptr junction; + Orientation orientation = Orientation::RIGHT; + uint64_t size = 1.25_mm; + std::set on_sheets; + bool offsheet_refs = true; + uuid_ptr bus; + + json serialize() const; + }; +} diff --git a/schematic/bus_ripper.cpp b/schematic/bus_ripper.cpp new file mode 100644 index 000000000..396d6075a --- /dev/null +++ b/schematic/bus_ripper.cpp @@ -0,0 +1,50 @@ +#include "bus_ripper.hpp" +#include "lut.hpp" +#include "sheet.hpp" +#include "block.hpp" +#include "json.hpp" + +namespace horizon { + + static const LutEnumStr orientation_lut = { + {"up", Orientation::UP}, + {"down", Orientation::DOWN}, + {"left", Orientation::LEFT}, + {"right", Orientation::RIGHT}, + }; + + BusRipper::BusRipper(const UUID &uu, const json &j, Sheet &sheet, Block &block): + uuid(uu), + junction(&sheet.junctions.at(j.at("junction").get())), + orientation(orientation_lut.lookup(j.at("orientation"))), + bus(&block.buses.at(j.at("bus").get())), + bus_member(&bus->members.at(j.at("bus_member").get())) + { + } + + BusRipper::BusRipper(const UUID &uu): uuid(uu) {} + + json BusRipper::serialize() const { + json j; + j["junction"] = (std::string)junction->uuid; + j["orientation"] = orientation_lut.lookup_reverse(orientation); + j["bus"] = (std::string)bus->uuid; + j["bus_member"] = (std::string)bus_member->uuid; + return j; + } + + Coordi BusRipper::get_connector_pos() const { + return junction->position+Coordi(1.25_mm, 1.25_mm); + } + + UUID BusRipper::get_uuid() const { + return uuid; + } + + + void BusRipper::update_refs(Sheet &sheet, Block &block) { + junction.update(sheet.junctions); + bus.update(block.buses); + bus_member.update(bus->members); + } +} diff --git a/schematic/bus_ripper.hpp b/schematic/bus_ripper.hpp new file mode 100644 index 000000000..6c0825902 --- /dev/null +++ b/schematic/bus_ripper.hpp @@ -0,0 +1,39 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "common.hpp" +#include "uuid_ptr.hpp" +#include "uuid_provider.hpp" +#include "junction.hpp" +#include "bus.hpp" +#include +#include +#include +#include + + +namespace horizon { + using json = nlohmann::json; + + class BusRipper : public UUIDProvider{ + public : + BusRipper(const UUID &uu, const json &j, class Sheet &sheet, class Block &block); + BusRipper(const UUID &uu); + virtual UUID get_uuid() const; + const UUID uuid; + + uuid_ptr junction; + Orientation orientation = Orientation::RIGHT; + uuid_ptr bus = nullptr; + uuid_ptr bus_member = nullptr; + unsigned int connection_count = 0; + + void update_refs(class Sheet &sheet, class Block &block); + Coordi get_connector_pos() const; + + + json serialize() const; + + UUID net_segment; //net segment from net, not from bus + }; +} diff --git a/schematic/frame.cpp b/schematic/frame.cpp new file mode 100644 index 000000000..264303717 --- /dev/null +++ b/schematic/frame.cpp @@ -0,0 +1,24 @@ +#include "frame.hpp" + +namespace horizon { + Frame::Frame() {} + Frame::Frame(const json &j){} + + static const std::map> frame_sizes = { + {Frame::Format::A4_LANDSCAPE, { 297_mm, 210_mm}} + }; + + uint64_t Frame::get_width() const { + return frame_sizes.at(format).first; + } + + uint64_t Frame::get_height() const { + return frame_sizes.at(format).second; + } + + std::pair Frame::get_bbox() const { + Coordi a; + std::tie(a.x, a.y) = frame_sizes.at(format); + return {Coordi(), a}; + } +} diff --git a/schematic/frame.hpp b/schematic/frame.hpp new file mode 100644 index 000000000..e535b334f --- /dev/null +++ b/schematic/frame.hpp @@ -0,0 +1,30 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "common.hpp" +#include +#include +#include + + +namespace horizon { + using json = nlohmann::json; + + class Frame { + public : + Frame(const json &j); + Frame(); + + enum class Format {NONE, A4_LANDSCAPE, A3_LANDSCAPE}; + uint64_t get_width() const; + uint64_t get_height() const; + + Format format = Format::A4_LANDSCAPE; + uint64_t border = 5_mm; + + std::pair get_bbox() const; + + json serialize() const; + + }; +} diff --git a/schematic/line_net.cpp b/schematic/line_net.cpp new file mode 100644 index 000000000..823407805 --- /dev/null +++ b/schematic/line_net.cpp @@ -0,0 +1,185 @@ +#include "line_net.hpp" +#include "lut.hpp" +#include "json.hpp" +#include "object.hpp" +#include "schematic_symbol.hpp" +#include "sheet.hpp" + +namespace horizon { + + LineNet::Connection::Connection(const json &j, Sheet &sheet) { + if(!j.at("junc").is_null()) { + junc = &sheet.junctions.at(j.at("junc").get()); + } + else if(!j.at("pin").is_null()) { + UUIDPath<2> pin_path(j.at("pin").get()); + symbol = &sheet.symbols.at(pin_path.at(0)); + pin = &symbol->symbol.pins.at(pin_path.at(1)); + } + else if(!j.at("bus_ripper").is_null()) { + bus_ripper = &sheet.bus_rippers.at(j.at("bus_ripper").get()); + } + else { + assert(false); + } + } + + UUIDPath<2 >LineNet::Connection::get_pin_path() const { + assert(junc==nullptr); + return UUIDPath<2>(symbol->uuid, pin->uuid); + } + + bool LineNet::Connection::is_junc() const { + if(junc) { + assert(!is_pin()); + assert(!is_bus_ripper()); + return true; + } + return false; + } + bool LineNet::Connection::is_bus_ripper() const { + if(bus_ripper) { + assert(!is_pin()); + assert(!is_junc()); + return true; + } + return false; + } + + bool LineNet::Connection::is_pin() const { + if(symbol) { + assert(pin); + assert(!is_junc()); + assert(!is_bus_ripper()); + return true; + } + return false; + } + + void LineNet::Connection::connect(Junction *j) { + junc = j; + symbol = nullptr; + pin = nullptr; + bus_ripper = nullptr; + } + + void LineNet::Connection::connect(SchematicSymbol *s, SymbolPin *p) { + junc = nullptr; + symbol = s; + pin = p; + bus_ripper = nullptr; + } + void LineNet::Connection::connect(BusRipper *r) { + junc = nullptr;; + symbol = nullptr; + pin = nullptr; + bus_ripper = r; + } + + void LineNet::Connection::update_refs(Sheet &sheet) { + junc.update(sheet.junctions); + symbol.update(sheet.symbols); + if(symbol) + pin.update(symbol->symbol.pins); + bus_ripper.update(sheet.bus_rippers); + } + + Coordi LineNet::Connection::get_position() const { + if(is_junc()) { + return junc->position; + } + else if(is_pin()) { + return symbol->placement.transform(pin->position); + } + else if(is_bus_ripper()) { + return bus_ripper->get_connector_pos(); + } + else { + assert(false); + } + } + + json LineNet::Connection::serialize() const { + json j; + j["junc"] = nullptr; + j["pin"] = nullptr; + j["bus_ripper"] = nullptr; + if(is_junc()) { + j["junc"] = (std::string)junc->uuid; + } + else if(is_pin()) { + j["pin"] = (std::string)get_pin_path(); + } + else if(is_bus_ripper()) { + j["bus_ripper"] = (std::string)bus_ripper->uuid; + } + else { + assert(false); + } + return j; + } + + UUID LineNet::Connection::get_net_segment() const { + if(is_junc()) { + return junc->net_segment; + } + else if(is_pin()) { + return pin->net_segment; + } + else if(is_bus_ripper()) { + return bus_ripper->net_segment; + } + else { + assert(false); + return UUID(); + } + } + + + LineNet::LineNet(const UUID &uu, const json &j, Sheet &sheet): + uuid(uu), + from(j["from"], sheet), + to(j["to"], sheet) + { + + } + + void LineNet::update_refs(Sheet &sheet) { + to.update_refs(sheet); + from.update_refs(sheet); + } + + bool LineNet::is_connected_to(const UUID &uu_sym, const UUID &uu_pin) const { + for(auto &it: {to, from}) { + if(it.symbol && (it.symbol->uuid == uu_sym) && (it.pin->uuid == uu_pin)) + return true; + } + return false; + } + + UUID LineNet::get_uuid() const { + return uuid; + } + + LineNet::LineNet(const UUID &uu): uuid(uu) {} + + json LineNet::serialize() const { + json j; + j["from"] = from.serialize(); + j["to"] = to.serialize(); + return j; + } + + bool LineNet::coord_on_line(const Coordi &p) const { + Coordi a = Coordi::min(from.get_position(), to.get_position()); + Coordi b = Coordi::max(from.get_position(), to.get_position()); + if(p.x >= a.x && p.x <= b.x && p.y >= a.y && p.y <= b.y) { //inside bbox + auto c = to.get_position()-from.get_position(); + auto d = p-from.get_position(); + if((c.dot(d))*(c.dot(d)) == c.mag_sq()*d.mag_sq()) { + return true; + } + } + return false; + } +} diff --git a/schematic/line_net.hpp b/schematic/line_net.hpp new file mode 100644 index 000000000..7125637e5 --- /dev/null +++ b/schematic/line_net.hpp @@ -0,0 +1,70 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "common.hpp" +#include "junction.hpp" +#include "object.hpp" +#include "uuid_provider.hpp" +#include "uuid_ptr.hpp" +#include "schematic_symbol.hpp" +#include "net.hpp" +#include "bus.hpp" +#include "power_symbol.hpp" +#include "bus_ripper.hpp" +#include +#include +#include + + +namespace horizon { + using json = nlohmann::json; + + + class LineNet : public UUIDProvider{ + public : + enum class End {TO, FROM}; + + LineNet(const UUID &uu, const json &j, class Sheet &sheet); + LineNet(const UUID &uu); + + void update_refs(class Sheet &sheet); + bool is_connected_to(const UUID &uu_sym, const UUID &uu_pin) const ; + virtual UUID get_uuid() const; + bool coord_on_line(const Coordi &coord) const; + + uuid_ptr net=nullptr; + uuid_ptr bus=nullptr; + UUID net_segment = UUID(); + + + const UUID uuid; + + class Connection { + public: + Connection() {} + Connection(const json &j, Sheet &sheet); + uuid_ptr junc = nullptr; + uuid_ptr symbol = nullptr; + uuid_ptr pin = nullptr; + uuid_ptr bus_ripper = nullptr; + + void connect(Junction *j); + void connect(BusRipper *r); + void connect(SchematicSymbol *j, SymbolPin *pin); + UUIDPath<2> get_pin_path() const ; + bool is_junc() const ; + bool is_pin() const ; + bool is_bus_ripper() const; + UUID get_net_segment() const; + void update_refs(class Sheet &sheet); + Coordi get_position() const; + json serialize() const; + }; + + Connection from; + Connection to; + + + json serialize() const; + }; +} diff --git a/schematic/net_label.cpp b/schematic/net_label.cpp new file mode 100644 index 000000000..0c29edf2d --- /dev/null +++ b/schematic/net_label.cpp @@ -0,0 +1,34 @@ +#include "net_label.hpp" +#include "lut.hpp" +#include "sheet.hpp" +#include "json.hpp" + +namespace horizon { + + static const LutEnumStr orientation_lut = { + {"up", Orientation::UP}, + {"down", Orientation::DOWN}, + {"left", Orientation::LEFT}, + {"right", Orientation::RIGHT}, + }; + + NetLabel::NetLabel(const UUID &uu, const json &j, Sheet &sheet): + uuid(uu), + junction(&sheet.junctions.at(j.at("junction").get())), + orientation(orientation_lut.lookup(j["orientation"])), + size(j.value("size", 2500000)), + offsheet_refs(j.value("offsheet_refs", true)) + { + } + + NetLabel::NetLabel(const UUID &uu): uuid(uu) {} + + json NetLabel::serialize() const { + json j; + j["junction"] = (std::string)junction->uuid; + j["orientation"] = orientation_lut.lookup_reverse(orientation); + j["size"] = size; + j["offsheet_refs"] = offsheet_refs; + return j; + } +} diff --git a/schematic/net_label.hpp b/schematic/net_label.hpp new file mode 100644 index 000000000..f276ad637 --- /dev/null +++ b/schematic/net_label.hpp @@ -0,0 +1,34 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "common.hpp" +#include "uuid_ptr.hpp" +#include "junction.hpp" +#include +#include +#include +#include + + +namespace horizon { + using json = nlohmann::json; + + class NetLabel { + public : + NetLabel(const UUID &uu, const json &j, class Sheet &sheet); + NetLabel(const UUID &uu); + + const UUID uuid; + + enum class Style {PLAIN, FLAG}; + Style style = Style::FLAG; + + uuid_ptr junction; + Orientation orientation = Orientation::RIGHT; + uint64_t size = 1.25_mm; + std::set on_sheets; + bool offsheet_refs = true; + + json serialize() const; + }; +} diff --git a/schematic/power_symbol.cpp b/schematic/power_symbol.cpp new file mode 100644 index 000000000..00247d7db --- /dev/null +++ b/schematic/power_symbol.cpp @@ -0,0 +1,36 @@ +#include "power_symbol.hpp" +#include "lut.hpp" +#include "json.hpp" +#include "sheet.hpp" + +namespace horizon { + + + PowerSymbol::PowerSymbol(const UUID &uu, const json &j, Sheet &sheet, Block &block): + uuid(uu), + junction(&sheet.junctions.at(j.at("junction").get())), + net(&block.nets.at(j.at("net").get())), + mirror(j.value("mirror", false)), + temp(false) + { + } + + UUID PowerSymbol::get_uuid() const { + return uuid; + } + + void PowerSymbol::update_refs(Sheet &sheet, Block &block) { + junction.update(sheet.junctions); + net.update(block.nets); + } + + PowerSymbol::PowerSymbol(const UUID &uu): uuid(uu) {} + + json PowerSymbol::serialize() const { + json j; + j["junction"] = (std::string)junction->uuid; + j["net"] = (std::string)net->uuid; + j["mirror"] = mirror; + return j; + } +} diff --git a/schematic/power_symbol.hpp b/schematic/power_symbol.hpp new file mode 100644 index 000000000..640e1569f --- /dev/null +++ b/schematic/power_symbol.hpp @@ -0,0 +1,38 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "common.hpp" +#include "object.hpp" +#include "position_provider.hpp" +#include "uuid_provider.hpp" +#include "block.hpp" +#include "uuid_ptr.hpp" +#include +#include +#include + + +namespace horizon { + using json = nlohmann::json; + + + class PowerSymbol: UUIDProvider{ + public : + PowerSymbol(const UUID &uu, const json &j, class Sheet &sheet, class Block &block); + PowerSymbol(const UUID &uu); + + const UUID uuid; + uuid_ptr junction; + uuid_ptr net; + bool mirror = false; + + + virtual UUID get_uuid() const ; + void update_refs(Sheet &sheet, Block &block); + + //not stored + bool temp; + + json serialize() const; + }; +} diff --git a/schematic/schematic.cpp b/schematic/schematic.cpp new file mode 100644 index 000000000..bd7a2bcfc --- /dev/null +++ b/schematic/schematic.cpp @@ -0,0 +1,690 @@ +#include "schematic.hpp" +#include +#include +#include "json.hpp" + +namespace horizon { + + static const LutEnumStr annotation_order_lut = { + {"down_right", Schematic::Annotation::Order::DOWN_RIGHT}, + {"right_down", Schematic::Annotation::Order::RIGHT_DOWN}, + }; + static const LutEnumStr annotation_mode_lut = { + {"sequential", Schematic::Annotation::Mode::SEQUENTIAL}, + {"sheet_100", Schematic::Annotation::Mode::SHEET_100}, + {"sheet_1000", Schematic::Annotation::Mode::SHEET_1000}, + }; + + Schematic::Annotation::Annotation() {} + + Schematic::Annotation::Annotation(const json &j) { + if(!j.is_null()) { + order = annotation_order_lut.lookup(j.at("order")); + mode = annotation_mode_lut.lookup(j.at("mode")); + fill_gaps = j.at("fill_gaps"); + keep = j.at("keep"); + } + } + + json Schematic::Annotation::serialize() const { + json j; + j["order"] = annotation_order_lut.lookup_reverse(order); + j["mode"] = annotation_mode_lut.lookup_reverse(mode); + j["fill_gaps"] = fill_gaps; + j["keep"] = keep; + return j; + } + + Schematic::Schematic(const UUID &uu, const json &j, Block &iblock, Object &pool): + uuid(uu), + block(&iblock), + name(j.at("name").get()), + annotation(j.value("annotation", json())) + { + { + const json &o = j["sheets"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + sheets.emplace(std::make_pair(u, Sheet(u, it.value(), *block, pool))); + } + } + update_refs(); + } + + Schematic Schematic::new_from_file(const std::string &filename, Block &block, Object &pool) { + json j; + std::ifstream ifs(filename); + if(!ifs.is_open()) { + throw std::runtime_error("file " +filename+ " not opened"); + } + ifs>>j; + ifs.close(); + return Schematic(UUID(j["uuid"].get()), j, block, pool); + } + + Schematic::Schematic(const UUID &uu, Block &bl): uuid(uu), block(&bl) { + auto suu = UUID::random(); + sheets.emplace(suu, suu); + } + + + void Schematic::autoconnect_symbol(Sheet *sheet, SchematicSymbol *sym) { + assert(sheet == &sheets.at(sheet->uuid)); + assert(sym == &sheet->symbols.at(sym->uuid)); + for(auto &it_pin: sym->symbol.pins) { + auto pin_pos = sym->placement.transform(it_pin.second.position); + if(sym->component->connections.count(UUIDPath<2>(sym->gate->uuid, it_pin.first))) + continue; //pin is connected, don't connect + for (auto it_junc = sheet->junctions.begin(); it_junc != sheet->junctions.end();) { + bool erase = false; + if(!it_junc->second.bus && it_junc->second.position == pin_pos && (it_junc->second.net || it_junc->second.connection_count>0)) { + erase = true; + if(it_junc->second.net == nullptr) { + it_junc->second.net = block->insert_net(); + } + sym->component->connections.emplace(UUIDPath<2>(sym->gate->uuid, it_pin.first), it_junc->second.net); + bool has_power_sym = false; + for(const auto &it_ps: sheet->power_symbols) { + if(it_ps.second.junction->uuid == it_junc->first) { + has_power_sym = true; + break; + } + } + if(has_power_sym) { + erase = false; + //create line + auto uu = UUID::random(); + auto *line = &sheet->net_lines.emplace(uu, uu).first->second; + line->net = it_junc->second.net; + line->from.connect(sym, &it_pin.second); + line->to.connect(&it_junc->second); + + } + else { + sheet->replace_junction(&it_junc->second, sym, &it_pin.second); + } + + } + if(erase) { + sheet->junctions.erase(it_junc++); + break; + } + else { + it_junc++; + } + } + for(auto &it_sym_other: sheet->symbols) { + auto *sym_other = &it_sym_other.second; + if(sym_other == sym) + continue; + for(auto &it_pin_other: sym_other->symbol.pins) { + auto pin_pos_other = sym_other->placement.transform(it_pin_other.second.position); + if(sym_other->component->connections.count(UUIDPath<2>(sym_other->gate->uuid, it_pin_other.first))) + continue; // other pin is connected, don't connect + + if(pin_pos == pin_pos_other) { + //create net + auto uu = UUID::random(); + auto *net = block->insert_net(); + + //connect pin + sym->component->connections.emplace(UUIDPath<2>(sym->gate->uuid, it_pin.first), net); + //connect other pin + sym_other->component->connections.emplace(UUIDPath<2>(sym_other->gate->uuid, it_pin_other.first), net); + + uu = UUID::random(); + auto *line = &sheet->net_lines.emplace(uu, uu).first->second; + line->net = net; + line->from.connect(sym, &it_pin.second); + line->to.connect(sym_other, &it_pin_other.second); + break; + } + } + } + for(auto &it_rip: sheet->bus_rippers) { + if(pin_pos == it_rip.second.get_connector_pos()) { + sym->component->connections.emplace(UUIDPath<2>(sym->gate->uuid, it_pin.first), it_rip.second.bus_member->net); + auto uu = UUID::random(); + auto *line = &sheet->net_lines.emplace(uu, uu).first->second; + line->net = it_rip.second.bus_member->net; + line->from.connect(sym, &it_pin.second); + line->to.connect(&it_rip.second); + break; + } + } + } + + } + + void Schematic::disconnect_symbol(Sheet *sheet, SchematicSymbol *sym) { + assert(sheet == &sheets.at(sheet->uuid)); + assert(sym == &sheet->symbols.at(sym->uuid)); + std::map pin_junctions; + for(auto &it_line: sheet->net_lines) { + LineNet *line = &it_line.second; + //if((line->to_symbol && line->to_symbol->uuid == it.uuid) || (line->from_symbol &&line->from_symbol->uuid == it.uuid)) { + for(auto it_ft: {&line->to, &line->from}) { + if(it_ft->symbol == sym) { + Junction *j = nullptr; + if(pin_junctions.count(it_ft->pin)) { + j = pin_junctions.at(it_ft->pin); + } + else { + auto uu = UUID::random(); + auto x = pin_junctions.emplace(it_ft->pin, &sheet->junctions.emplace(uu, uu).first->second); + j = x.first->second; + } + auto c = it_ft->get_position(); + j->position = c; + it_ft->connect(j); + } + } + for (auto it_conn = sym->component->connections.cbegin(); it_conn != sym->component->connections.cend();) { + if(it_conn->first.at(0) == sym->gate->uuid) { + sym->component->connections.erase(it_conn++); + } + else { + it_conn++; + + } + } + } + } + + void Schematic::smash_symbol(Sheet *sheet, SchematicSymbol *sym) { + assert(sheet == &sheets.at(sheet->uuid)); + assert(sym == &sheet->symbols.at(sym->uuid)); + if(sym->smashed) + return; + sym->smashed = true; + for(const auto &it: sym->pool_symbol->texts) { + auto uu = UUID::random(); + auto &x = sheet->texts.emplace(uu, uu).first->second; + x.from_smash = true; + x.overridden = true; + x.text = it.second.text; + x.layer = it.second.layer; + x.position = sym->placement.transform(it.second.position); + x.orientation = it.second.orientation; + x.size = it.second.size; + x.width = it.second.width; + sym->texts.push_back(&x); + } + } + + void Schematic::unsmash_symbol(Sheet *sheet, SchematicSymbol *sym) { + assert(sheet == &sheets.at(sheet->uuid)); + assert(sym == &sheet->symbols.at(sym->uuid)); + if(!sym->smashed) + return; + sym->smashed = false; + for(auto &it: sym->texts) { + if(it->from_smash) { + sheet->texts.erase(it->uuid); //expand will delete from sym->texts + } + } + } + + + + + void Schematic::expand(bool careful) { + for(auto &it_net: block->nets) { + it_net.second.is_power_forced = false; + it_net.second.is_bussed = false; + } + for(auto &it_bus: block->buses) { + for(auto &it_mem: it_bus.second.members) { + it_mem.second.net->is_bussed = true; + } + } + + for(auto &it_sheet: sheets) { + Sheet &sheet = it_sheet.second; + for(auto &it_sym: sheet.power_symbols) { + it_sym.second.net->is_power = true; + it_sym.second.net->is_power_forced = true; + } + } + + if(!careful) + block->vacuum_nets(); + + block->update_connection_count(); //also sets has_bus_rippers to false + for(auto &it: block->buses) { + it.second.is_referenced = false; + } + + for(auto &it_sheet: sheets) { + Sheet &sheet = it_sheet.second; + for(auto &it_rip: sheet.bus_rippers) { + it_rip.second.bus_member->net->has_bus_rippers = true; + it_rip.second.bus->is_referenced = true; + } + for(auto &it_la: sheet.bus_labels) { + it_la.second.bus->is_referenced = true; + } + } + + //collect gates + if(!careful) { + std::set> gates; + for(const auto &it_component: block->components) { + for(const auto &it_gate: it_component.second.entity->gates) { + gates.emplace(it_component.first, it_gate.first); + } + } + + for(auto &it_sheet: sheets) { + Sheet &sheet = it_sheet.second; + sheet.warnings.clear(); + + std::vector keys; + keys.reserve(sheet.symbols.size()); + for(const auto &it: sheet.symbols) { + keys.push_back(it.first); + } + for(const auto &uu: keys) { + const auto &sym = sheet.symbols.at(uu); + if(gates.count({sym.component->uuid, sym.gate->uuid})) { + //all fine + gates.erase({sym.component->uuid, sym.gate->uuid}); + } + else { //duplicate symbol + sheet.symbols.erase(uu); + } + } + } + } + + + for(auto &it_sheet: sheets) { + Sheet &sheet = it_sheet.second; + for(auto &it_sym: sheet.symbols) { + auto &texts = it_sym.second.texts; + texts.erase(std::remove_if(texts.begin(), texts.end(), [&sheet](const auto &a){ + return sheet.texts.count(a.uuid) == 0; + }), texts.end()); + } + sheet.expand_symbols(); + } + + + + + for(auto &it_sheet: sheets) { + Sheet &sheet = it_sheet.second; + + for(auto &it_junc: sheet.junctions) { + it_junc.second.net = nullptr; + it_junc.second.bus = nullptr; + it_junc.second.temp = false; + } + for(auto &it_line: sheet.net_lines) { + it_line.second.net = nullptr; + it_line.second.bus = nullptr; + } + + + sheet.delete_dependants(); + if(!careful) { + sheet.simplify_net_lines(); + } + sheet.propagate_net_segments(); + + auto net_segments = sheet.analyze_net_segments(); + + + for(auto &it_sym: sheet.power_symbols) { + if(it_sym.second.junction->net_segment) { + //net segments of single power symbols aren't included in net_segments + if(net_segments.count(it_sym.second.junction->net_segment)) { + auto &ns = net_segments.at(it_sym.second.junction->net_segment); + if(ns.net == nullptr) { + ns.net = it_sym.second.net; + } + if(ns.net != it_sym.second.net) { + throw std::runtime_error("illegal power connection"); + } + } + } + } + for(auto &it_rip: sheet.bus_rippers) { + if(it_rip.second.net_segment) { + if(net_segments.count(it_rip.second.net_segment)) { + auto &ns = net_segments.at(it_rip.second.net_segment); + if(ns.net == nullptr) { + ns.net = it_rip.second.bus_member->net; + } + if(ns.net != it_rip.second.bus_member->net) { + throw std::runtime_error("illegal bus ripper connection"); + } + } + } + } + for(auto &it_la: sheet.bus_labels) { + if(it_la.second.junction->net_segment) { + //net segments of single power symbols aren't included in net_segments + if(net_segments.count(it_la.second.junction->net_segment)) { + auto &ns = net_segments.at(it_la.second.junction->net_segment); + if(ns.net != nullptr) { + throw std::runtime_error("bus/net conflict"); + } + if(ns.bus== nullptr) { + ns.bus = it_la.second.bus; + } + if(ns.bus != it_la.second.bus) { + throw std::runtime_error("illegal bus connection"); + } + } + } + } + + for(auto &it_sym: sheet.symbols) { + SchematicSymbol &schsym = it_sym.second; + Component *comp = schsym.component; + for(const auto &it_conn: comp->connections) { + Gate &gate_conn = comp->entity->gates.at(it_conn.first.at(0)); + Pin &pin = gate_conn.unit->pins.at(it_conn.first.at(1)); + if((gate_conn.uuid == schsym.gate->uuid) && it_conn.second.net) {//connection with net + SymbolPin &sympin = schsym.symbol.pins.at(pin.uuid); + sympin.connector_style = SymbolPin::ConnectorStyle::NONE; + if(sympin.net_segment) { + auto &ns = net_segments.at(sympin.net_segment); + if(ns.is_bus()) { + throw std::runtime_error("illegal bus-pin connection"); + } + if(ns.net == nullptr) { + ns.net = it_conn.second.net; + } + if(ns.net != it_conn.second.net) { + throw std::runtime_error("illegal connection"); + } + } + } + } + } + for(auto &it_line: sheet.net_lines) { + it_line.second.net = net_segments.at(it_line.second.net_segment).net; + it_line.second.bus = net_segments.at(it_line.second.net_segment).bus; + } + for(auto &it_junc: sheet.junctions) { + if(net_segments.count(it_junc.second.net_segment)) { + it_junc.second.net = net_segments.at(it_junc.second.net_segment).net; + it_junc.second.bus = net_segments.at(it_junc.second.net_segment).bus; + } + } + } + + + for(auto &it_sheet: sheets) { + Sheet &sheet = it_sheet.second; + sheet.analyze_net_segments(true); + } + + //warn juncs + for(auto &it_sheet: sheets) { + Sheet &sheet = it_sheet.second; + for(auto &it_sym: sheet.symbols) { + SchematicSymbol &schsym = it_sym.second; + for(auto &it_pin: schsym.symbol.pins) { + auto pin_pos = schsym.placement.transform(it_pin.second.position); + for(auto &it_junc: sheet.junctions) { + Junction &junc = it_junc.second; + if(junc.position == pin_pos) { + sheet.warnings.emplace_back(junc.position, "Pin on junction"); + } + } + for(auto &it_line: sheet.net_lines) { + LineNet &line = it_line.second; + if(line.coord_on_line(pin_pos)) { + if(!line.is_connected_to(it_sym.first, it_pin.first)) { + sheet.warnings.emplace_back(pin_pos, "Pin on Line"); + } + } + } + } + } + } + + //warn bus rippers + for(auto &it_sheet: sheets) { + Sheet &sheet = it_sheet.second; + for(auto &it_rip: sheet.bus_rippers) { + BusRipper &ripper = it_rip.second; + if(ripper.junction->bus != ripper.bus) { + sheet.warnings.emplace_back(ripper.junction->position, "Bus ripper connected to wrong net line"); + } + } + } + + std::map> nets_on_sheets; + for(auto &it: sheets) { + auto nsinfo = it.second.analyze_net_segments(); + for(auto &it_ns: nsinfo) { + if(it_ns.second.net) { + auto net_uuid = it_ns.second.net->uuid; + if(nets_on_sheets.count(net_uuid) == 0) { + nets_on_sheets.emplace(net_uuid, std::set()); + } + nets_on_sheets.at(net_uuid).insert(it.second.index); + } + } + } + for(auto &it: sheets) { + for(auto &it_label: it.second.net_labels) { + if(it_label.second.junction->net) { + if(nets_on_sheets.count(it_label.second.junction->net->uuid)) { + it_label.second.on_sheets = nets_on_sheets.at(it_label.second.junction->net->uuid); + it_label.second.on_sheets.erase(it.second.index); + } + } + } + } + + + } + + void Schematic::annotate() { + if(!annotation.keep) { + for(auto &it: block->components) { + it.second.refdes = "?"; + } + } + + std::map> refdes; + for(const auto &it: block->components) { + refdes[it.second.entity->prefix]; + } + + + std::vector sheets_sorted; + sheets_sorted.reserve(sheets.size()); + std::transform(sheets.begin(), sheets.end(), std::back_inserter(sheets_sorted), [](auto &a){return &a.second;}); + std::sort(sheets_sorted.begin(), sheets_sorted.end(), [](const auto a, const auto b){return a->indexindex;}); + + + unsigned int sheet_offset = 0; + unsigned int sheet_incr = 0; + + for(auto sheet: sheets_sorted) { + if(annotation.mode != Annotation::Mode::SEQUENTIAL) { + for(auto &it: refdes) { + it.second.clear(); + } + if(annotation.mode == Annotation::Mode::SHEET_100) { + sheet_offset = 100*sheet->index; + sheet_incr = 100; + } + else if(annotation.mode == Annotation::Mode::SHEET_1000) { + sheet_offset = 1000*sheet->index; + sheet_incr = 1000; + } + } + + + + std::vector symbols; + symbols.reserve(sheet->symbols.size()); + std::transform(sheet->symbols.begin(), sheet->symbols.end(), std::back_inserter(symbols), [](auto &a){return &a.second;}); + switch(annotation.order) { + case Annotation::Order::RIGHT_DOWN : + std::sort(symbols.begin(), symbols.end(), [](const auto a, const auto b){return a->placement.shift.xplacement.shift.x;}); + std::stable_sort(symbols.begin(), symbols.end(), [](const auto a, const auto b){return a->placement.shift.y>b->placement.shift.y;}); + break; + case Annotation::Order::DOWN_RIGHT : + std::sort(symbols.begin(), symbols.end(), [](const auto a, const auto b){return a->placement.shift.y>b->placement.shift.y;}); + std::stable_sort(symbols.begin(), symbols.end(), [](const auto a, const auto b){return a->placement.shift.xplacement.shift.x;}); + break; + } + + for(const auto sym: symbols) { + auto &v = refdes[sym->component->entity->prefix]; + if(sym->component->refdes.find('?') == std::string::npos) { //already annotated + auto ss = sym->component->refdes.substr(sym->component->entity->prefix.size()); + int si=-1; + try { + si = std::stoi(ss); + } + catch (const std::invalid_argument &e) { + sym->component->refdes = "?"; + } + if(si>0) { + v.push_back(si); + std::sort(v.begin(), v.end()); + } + } + } + + for(const auto sym: symbols) { + auto &v = refdes[sym->component->entity->prefix]; + if(sym->component->refdes.find('?') != std::string::npos) { //needs annotatation + unsigned int n = 1+sheet_offset; + if(v.size() != 0) { + if(annotation.fill_gaps && v.size()>=2) { + bool hole = false; + for(auto it = v.begin(); itindex) { + n = sheet_offset+1; + } + v.push_back(n); + sym->component->refdes = sym->component->entity->prefix + std::to_string(n); + } + std::sort(v.begin(), v.end()); + } + + //for(auto &it_sym: it_sheet.second.symbols) { + // + //} + } + } + + Schematic::Schematic(const Schematic &sch): + uuid(sch.uuid), + block(sch.block), + name(sch.name), + sheets(sch.sheets), + annotation(sch.annotation) + { + update_refs(); + } + + void Schematic::operator=(const Schematic &sch) { + uuid = sch.uuid; + block = sch.block; + name = sch.name; + sheets = sch.sheets; + annotation = sch.annotation; + update_refs(); + } + + void Schematic::update_refs() { + for(auto &it_sheet: sheets) { + Sheet &sheet = it_sheet.second; + for(auto &it_sym: sheet.symbols) { + SchematicSymbol &schsym = it_sym.second; + schsym.component.update(block->components); + schsym.gate.update(schsym.component->entity->gates); + for(auto &it_pin: schsym.symbol.pins) { + for(auto &it_conn: it_pin.second.connected_net_lines) { + it_conn.second = &sheet.net_lines.at(it_conn.first); + } + } + for(auto &it_text: schsym.texts) { + it_text.update(sheet.texts); + } + } + for(auto &it_line: sheet.net_lines) { + LineNet &line = it_line.second; + line.update_refs(sheet); + line.net.update(block->nets); + line.bus.update(block->buses); + } + for(auto &it_junc: sheet.junctions) { + it_junc.second.net.update(block->nets); + it_junc.second.bus.update(block->buses); + } + for(auto &it_sym: sheet.power_symbols) { + it_sym.second.update_refs(sheet, *block); + } + for(auto &it_label: sheet.net_labels) { + it_label.second.junction.update(sheet.junctions); + } + for(auto &it_bus_label: sheet.bus_labels) { + it_bus_label.second.junction.update(sheet.junctions); + it_bus_label.second.bus.update(block->buses); + } + for(auto &it_sym: sheet.bus_rippers) { + it_sym.second.update_refs(sheet, *block); + } + /*for(auto &it_junc: sheet.junction_pins) { + JunctionPin &junc = it_junc.second; + junc.sym = &sheet.symbols.at(junc.pin_path.at(0)); + }*/ + } + } + /* + void Schematic::merge_nets(Net *net, Net *into) { + assert(net->uuid==nets.at(net->uuid).uuid); + assert(into->uuid==nets.at(into->uuid).uuid); + for(auto &it_comp: components) { + for(auto &it_conn: it_comp.second.connections) { + if(it_conn.second.net == net) { + it_conn.second.net = into; + } + } + } + nets.erase(net->uuid); + }*/ + + json Schematic::serialize() const { + json j; + j["type"] = "schematic_block"; + j["uuid"] = (std::string)uuid; + j["block"] = (std::string)block->uuid; + j["name"] = name; + j["annotation"] = annotation.serialize(); + + + j["sheets"] = json::object(); + for(const auto &it : sheets) { + j["sheets"][(std::string)it.first] = it.second.serialize(); + } + + return j; + } +} diff --git a/schematic/schematic.hpp b/schematic/schematic.hpp new file mode 100644 index 000000000..6281fc90b --- /dev/null +++ b/schematic/schematic.hpp @@ -0,0 +1,62 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "object.hpp" +#include "unit.hpp" +#include "block.hpp" +#include "sheet.hpp" +#include +#include +#include + +namespace horizon { + using json = nlohmann::json; + + class Schematic { + private : + Schematic(const UUID &uu, const json &, Block &block, Object &pool); + unsigned int update_nets(); + + + public : + static Schematic new_from_file(const std::string &filename, Block &block, Object &pool); + Schematic(const UUID &uu, Block &block); + void expand(bool careful=false); + + Schematic(const Schematic &sch); + void operator=(const Schematic &sch); + void update_refs(); + void merge_nets(Net *net, Net *into); + void disconnect_symbol(Sheet *sheet, SchematicSymbol *sym); + void autoconnect_symbol(Sheet *sheet, SchematicSymbol *sym); + void smash_symbol(Sheet *sheet, SchematicSymbol *sym); + void unsmash_symbol(Sheet *sheet, SchematicSymbol *sym); + + + UUID uuid; + Block *block; + std::string name; + std::map sheets; + + class Annotation { + public: + Annotation(const json &j); + Annotation(); + enum class Order {RIGHT_DOWN, DOWN_RIGHT}; + Order order = Order::RIGHT_DOWN; + + enum class Mode {SEQUENTIAL, SHEET_100, SHEET_1000}; + Mode mode = Mode::SHEET_100; + + bool fill_gaps = true; + bool keep = true; + json serialize() const; + }; + + Annotation annotation; + void annotate(); + + json serialize() const; + }; + +} diff --git a/schematic/schematic_symbol.cpp b/schematic/schematic_symbol.cpp new file mode 100644 index 000000000..3f94358f1 --- /dev/null +++ b/schematic/schematic_symbol.cpp @@ -0,0 +1,72 @@ +#include "schematic_symbol.hpp" +#include "json.hpp" +#include "part.hpp" + +namespace horizon { + + SchematicSymbol::SchematicSymbol(const UUID &uu, const json &j, Block &block, Pool &pool): + uuid(uu), + pool_symbol(pool.get_symbol(j.at("symbol").get())), + symbol(*pool_symbol), + component(&block.components.at(j.at("component").get())), + gate(&component->entity->gates.at(j.at("gate").get())), + placement(j.at("placement")), + smashed(j.value("smashed", false)) + { + if(j.count("texts")) { + const json &o = j.at("texts"); + for (auto it = o.cbegin(); it != o.cend(); ++it) { + texts.emplace_back(UUID(it.value().get())); + } + } + } + SchematicSymbol::SchematicSymbol(const UUID &uu, Symbol *sym): uuid(uu), pool_symbol(sym), symbol(*sym) {} + + json SchematicSymbol::serialize() const { + json j; + j["component"] = (std::string)component.uuid; + j["gate"] = (std::string)gate.uuid; + j["symbol"] = (std::string)pool_symbol->uuid; + j["placement"] = placement.serialize(); + j["smashed"] = smashed; + j["texts"] = json::array(); + for(const auto &it: texts) { + j["texts"].push_back((std::string)it->uuid); + } + + return j; + } + + UUID SchematicSymbol::get_uuid() const { + return uuid; + } + + std::string SchematicSymbol::replace_text(const std::string &t, bool *replaced) const { + if(replaced) + *replaced = false; + if(t == "$REFDES") { + if(replaced) + *replaced = true; + return component->refdes + gate->suffix; + } + else if(t == "$VALUE") { + if(replaced) + *replaced = true; + if(component->part) + return component->part->get_value(); + else + return component->value; + } + else if(t == "$MPN") { + if(component->part) { + if(replaced) + *replaced = true; + return component->part->get_MPN(); + } + return t; + } + else { + return t; + } + } +} diff --git a/schematic/schematic_symbol.hpp b/schematic/schematic_symbol.hpp new file mode 100644 index 000000000..7388b54b3 --- /dev/null +++ b/schematic/schematic_symbol.hpp @@ -0,0 +1,39 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "object.hpp" +#include "unit.hpp" +#include "symbol.hpp" +#include "block.hpp" +#include "uuid_ptr.hpp" +#include "placement.hpp" +#include "uuid_provider.hpp" +#include "pool.hpp" +#include +#include +#include + +namespace horizon { + using json = nlohmann::json; + + class SchematicSymbol: public UUIDProvider { + public : + SchematicSymbol(const UUID &uu, const json &, Block &block, Pool &pool); + SchematicSymbol(const UUID &uu, Symbol *sym); + UUID uuid; + Symbol *pool_symbol; + Symbol symbol; + uuid_ptr component; + uuid_ptr gate; + Placement placement; + std::vector> texts; + bool smashed = false; + + std::string replace_text(const std::string &t, bool *replaced = nullptr) const; + + UUID get_uuid() const override; + json serialize() const; + + }; + +} diff --git a/schematic/sheet.cpp b/schematic/sheet.cpp new file mode 100644 index 000000000..8562e88d8 --- /dev/null +++ b/schematic/sheet.cpp @@ -0,0 +1,504 @@ +#include "sheet.hpp" +#include "json.hpp" +#include "part.hpp" + +namespace horizon { + + Sheet::Sheet(const UUID &uu, const json &j, Block &block, Object &pool): + uuid(uu), + name(j.at("name").get()), + index(j.at("index").get()) + { + { + const json &o = j["junctions"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + junctions.emplace(std::make_pair(u, Junction(u, it.value()))); + } + } + { + const json &o = j["symbols"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + symbols.emplace(std::make_pair(u, SchematicSymbol(u, it.value(), block, dynamic_cast(pool)))); + } + } + /*{ + const json &o = j["junction_pins"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + junction_pins.emplace(std::make_pair(u, JunctionPin(u, it.value(), *this))); + } + }*/ + + { + const json &o = j["texts"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + texts.emplace(std::make_pair(u, Text(u, it.value()))); + } + } + { + const json &o = j["net_labels"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + net_labels.emplace(std::make_pair(u, NetLabel(u, it.value(), *this))); + } + } + { + const json &o = j["power_symbols"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + power_symbols.emplace(std::make_pair(u, PowerSymbol(u, it.value(), *this, block))); + } + } + { + const json &o = j["bus_rippers"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + bus_rippers.emplace(std::make_pair(u, BusRipper(u, it.value(), *this, block))); + } + } + { + const json &o = j["net_lines"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + net_lines.emplace(std::make_pair(u, LineNet(u, it.value(), *this))); + } + } + { + const json &o = j["bus_labels"]; + for (auto it = o.cbegin(); it != o.cend(); ++it) { + auto u = UUID(it.key()); + bus_labels.emplace(std::make_pair(u, BusLabel(u, it.value(), *this, block))); + } + } + } + + Sheet::Sheet(const UUID &uu): uuid(uu), name("First sheet"), index(1) {} + + LineNet *Sheet::split_line_net(LineNet *it, Junction *ju) { + auto uu = UUID::random(); + LineNet *li = &net_lines.emplace(std::make_pair(uu, uu)).first->second; + li->from.connect(ju); + li->to = it->to; + li->net = it->net; + li->bus = it->bus; + + it->to.connect(ju); + return li; + } + + void Sheet::merge_net_lines(LineNet *a, LineNet *b, Junction *ju) { + if(a->from.junc==ju) { + if(b->from.junc==ju) { + a->from = b->to; + } + else { + a->from = b->from; + } + } + else { + assert(a->to.junc==ju); + if(b->from.junc==ju) { + a->to = b->to; + } + else { + a->to = b->from; + } + } + + //delete junction + //delete b + junctions.erase(ju->uuid); + net_lines.erase(b->uuid); + } + + void Sheet::expand_symbols(void) { + for(auto &it_sym: symbols) { + SchematicSymbol &schsym = it_sym.second; + if(schsym.symbol.unit->uuid != schsym.gate->unit->uuid) { + throw std::logic_error("unit mismatch"); + } + schsym.symbol = *schsym.pool_symbol; + schsym.symbol.expand(); + for(const auto &it_name_index: schsym.component->pin_names) { + if(it_name_index.first.at(0) == schsym.gate->uuid) { + if(it_name_index.second != -1) { + auto pin_uuid = it_name_index.first.at(1); + schsym.symbol.pins.at(pin_uuid).name = schsym.gate->unit->pins.at(pin_uuid).names.at(it_name_index.second); + } + } + } + if(schsym.component->part) { + for(const auto &it_pad_map: schsym.component->part->pad_map) { + if(it_pad_map.second.gate == schsym.gate) { + if(schsym.symbol.pins.count(it_pad_map.second.pin->uuid)) { + schsym.symbol.pins.at(it_pad_map.second.pin->uuid).pad = schsym.component->part->package->pads.at(it_pad_map.first).name; + } + } + + } + } + for(auto &it_text: schsym.symbol.texts) { + it_text.second.text = schsym.replace_text(it_text.second.text); + } + + for(auto &it_text: schsym.texts) { + it_text->text_override = schsym.replace_text(it_text->text, &it_text->overridden); + } + } + for(auto &it_line: net_lines) { + LineNet &line = it_line.second; + line.update_refs(*this); + } + } + + void Sheet::simplify_net_lines() { + unsigned int n_merged = 1; + while(n_merged) { + n_merged = 0; + for(auto &it_junc: junctions) { + it_junc.second.net = nullptr; + it_junc.second.connection_count = 0; + } + for(auto &it_rip: bus_rippers) { + it_rip.second.connection_count = 0; + } + std::set junctions_with_label; + for(const auto &it: net_labels) { + junctions_with_label.emplace(it.second.junction->uuid); + } + for(const auto &it: bus_labels) { + junctions_with_label.emplace(it.second.junction->uuid); + } + for(const auto &it: bus_rippers) { + junctions_with_label.emplace(it.second.junction->uuid); + } + for(const auto &it: power_symbols) { + junctions_with_label.emplace(it.second.junction->uuid); + it.second.junction->connection_count++; + } + + std::map> junction_connections; + for(auto &it_line: net_lines) { + it_line.second.net = nullptr; + for(auto &it_ft: {it_line.second.from, it_line.second.to}) { + if(it_ft.is_junc()) { + it_ft.junc->connection_count++; + if(!junction_connections.count(it_ft.junc.uuid)) { + junction_connections.emplace(it_ft.junc.uuid, std::set()); + } + junction_connections.at(it_ft.junc.uuid).emplace(it_line.first); + } + else if(it_ft.is_bus_ripper()) { + it_ft.bus_ripper->connection_count++; + } + } + } + for(const auto &it: junction_connections) { + if(it.second.size() == 2) { + const auto &connections = it.second; + auto itc = connections.begin(); + if(net_lines.count(*itc) == 0) + continue; + auto &line_a = net_lines.at(*itc); + itc++; + if(net_lines.count(*itc) == 0) + continue; + auto &line_b = net_lines.at(*itc); + + auto va = line_a.to.get_position()-line_a.from.get_position(); + auto vb = line_b.to.get_position()-line_b.from.get_position(); + + if((va.dot(vb))*(va.dot(vb)) == va.mag_sq()*vb.mag_sq()) { + if(junctions_with_label.count(it.first) == 0) { + merge_net_lines(&line_a, &line_b, &junctions.at(it.first)); + n_merged++; + } + } + } + } + } + } + + void Sheet::delete_dependants() { + auto label_it = net_labels.begin(); + while(label_it != net_labels.end()) { + if(junctions.count(label_it->second.junction.uuid) == 0) { + label_it = net_labels.erase(label_it); + } + else { + label_it++; + } + } + auto bus_label_it = bus_labels.begin(); + while(bus_label_it != bus_labels.end()) { + if(junctions.count(bus_label_it->second.junction.uuid) == 0) { + bus_label_it = bus_labels.erase(bus_label_it); + } + else { + bus_label_it++; + } + } + auto ps_it = power_symbols.begin(); + while(ps_it != power_symbols.end()) { + if(junctions.count(ps_it->second.junction.uuid) == 0) { + ps_it = power_symbols.erase(ps_it); + } + else { + ps_it++; + } + } + } + + void Sheet::propagate_net_segments() { + for(auto &it: junctions) { + it.second.net_segment = UUID(); + } + for(auto &it: bus_rippers) { + it.second.net_segment = UUID(); + } + for(auto &it: net_lines) { + it.second.net_segment = UUID(); + } + for(auto &it: symbols) { + for(auto &it_pin: it.second.symbol.pins) { + it_pin.second.net_segment = UUID(); + it_pin.second.connected_net_lines.clear(); + } + } + unsigned int run = 1; + while(run) { + run = 0; + for(auto &it: net_lines) { + if(!it.second.net_segment) { + it.second.net_segment = UUID::random(); + run = 1; + break; + } + } + if(run == 0) { + break; + } + unsigned int n_assigned = 1; + while(n_assigned) { + n_assigned = 0; + for(auto &it: net_lines) { + if(it.second.net_segment) { //net line with uuid + for(auto &it_ft: {it.second.from, it.second.to}) { + if(it_ft.is_junc() && !it_ft.junc->net_segment) { + it_ft.junc->net_segment = it.second.net_segment; + n_assigned++; + } + else if(it_ft.is_pin() && !it_ft.pin->net_segment) { + it_ft.pin->net_segment = it.second.net_segment; + it_ft.pin->connected_net_lines.emplace(it.first, &it.second); + n_assigned++; + } + else if(it_ft.is_bus_ripper() && !it_ft.bus_ripper->net_segment) { + it_ft.bus_ripper->net_segment = it.second.net_segment; + n_assigned++; + } + } + } + else { //net line without uuid + for(auto &it_ft: {it.second.from, it.second.to}) { + if(it_ft.is_junc() && it_ft.junc->net_segment) { + it.second.net_segment = it_ft.junc->net_segment; + n_assigned++; + } + else if(it_ft.is_pin() && it_ft.pin->net_segment) { + it.second.net_segment = it_ft.pin->net_segment; + it_ft.pin->connected_net_lines.emplace(it.first, &it.second); + n_assigned++; + } + else if(it_ft.is_bus_ripper() && it_ft.bus_ripper->net_segment) { + it.second.net_segment = it_ft.bus_ripper->net_segment; + n_assigned++; + } + } + } + } + } + } + //fix single junctions + for(auto &it: junctions) { + if(!it.second.net_segment) { + it.second.net_segment = UUID::random(); + } + } + } + + NetSegmentInfo::NetSegmentInfo(Junction *ju): position(ju->position), net(ju->net), bus(ju->bus) {} + NetSegmentInfo::NetSegmentInfo(LineNet *li): position((li->to.get_position()+li->from.get_position())/2), net(li->net), bus(li->bus) {} + bool NetSegmentInfo::is_bus() const { + if(bus) { + assert(!net); + return true; + } + return false; + } + + std::map Sheet::analyze_net_segments(bool place_warnings) { + std::map net_segments; + for(auto &it: net_lines) { + auto ns = it.second.net_segment; + net_segments.emplace(ns, NetSegmentInfo(&it.second)); + } + for(auto &it: junctions) { + auto ns = it.second.net_segment; + net_segments.emplace(ns, NetSegmentInfo(&it.second)); + } + for(const auto &it: net_labels) { + if(net_segments.count(it.second.junction->net_segment)) { + net_segments.at(it.second.junction->net_segment).has_label = true; + } + } + for(const auto &it: power_symbols) { + if(net_segments.count(it.second.junction->net_segment)) { + net_segments.at(it.second.junction->net_segment).has_power_sym = true; + net_segments.at(it.second.junction->net_segment).has_label = true; + } + } + for(const auto &it: bus_rippers) { + if(net_segments.count(it.second.net_segment)) { + net_segments.at(it.second.net_segment).has_label = true; + } + } + + if(!place_warnings) + return net_segments; + + for(const auto &it: net_segments) { + if(!it.second.has_label) { + //std::cout<< "ns no label" << (std::string)it.second.net << std::endl; + if(it.second.net && it.second.net->is_named()) { + warnings.emplace_back(it.second.position, "Label missing"); + } + } + if(!it.second.has_power_sym) { + //std::cout<< "ns no label" << (std::string)it.second.net << std::endl; + if(it.second.net && it.second.net->is_power) { + warnings.emplace_back(it.second.position, "Power sym missing"); + } + } + } + std::set nets_unnamed, ambigous_nets; + + for(const auto &it: net_segments) { + if(it.second.net && !it.second.net->is_named()) { + auto x= nets_unnamed.emplace(it.second.net); + if(x.second == false) { //already exists + ambigous_nets.emplace(it.second.net); + } + } + } + for(const auto &it: net_segments) { + if(ambigous_nets.count(it.second.net)) { + if(!it.second.has_label) + warnings.emplace_back(it.second.position, "Ambigous nets"); + } + } + for(const auto &it: net_lines) { + if(it.second.from.get_position() == it.second.to.get_position()) { + warnings.emplace_back(it.second.from.get_position(), "Zero length line"); + } + } + return net_segments; + } + + std::set> Sheet::get_pins_connected_to_net_segment(const UUID &uu_segment) { + std::set> r; + for(const auto &it_sym: symbols) { + for(const auto &it_pin: it_sym.second.symbol.pins) { + if(it_pin.second.net_segment == uu_segment) { + r.emplace(it_sym.second.component->uuid, it_sym.second.gate->uuid, it_pin.first); + } + } + } + return r; + } + + void Sheet::replace_junction(Junction *j, SchematicSymbol *sym, SymbolPin *pin) { + for(auto &it_line: net_lines) { + for(auto it_ft: {&it_line.second.from, &it_line.second.to}) { + if(it_ft->junc == j) { + it_ft->connect(sym, pin); + } + } + } + } + + Junction *Sheet::replace_bus_ripper(BusRipper *rip) { + auto uu = UUID::random(); + Junction *j = &junctions.emplace(uu, uu).first->second; + j->net = rip->bus_member->net; + j->position = rip->get_connector_pos(); + + for(auto &it_line: net_lines) { + for(auto it_ft: {&it_line.second.from, &it_line.second.to}) { + if(it_ft->bus_ripper == rip) { + it_ft->connect(j); + } + } + } + return j; + + } + + /*void Sheet::connect(SchematicSymbol *sym, SymbolPin *pin, PowerSymbol *power_sym) { + auto uu = UUID::random(); + + //connect pin + sym->component->connections.emplace(UUIDPath<2>(sym->gate->uuid, pin->uuid), power_sym->net); + + uu = UUID::random(); + auto *line = &net_lines.emplace(uu, uu).first->second; + line->net = power_sym->net; + line->from.connect(sym, pin); + line->to.connect(power_sym); + }*/ + + json Sheet::serialize() const { + json j; + j["name"] = name; + j["index"] = index; + j["symbols"] = json::object(); + + for(const auto &it : symbols) { + j["symbols"][(std::string)it.first] = it.second.serialize(); + } + + j["junctions"] = json::object(); + for(const auto &it : junctions) { + j["junctions"][(std::string)it.first] = it.second.serialize(); + } + j["net_lines"] = json::object(); + for(const auto &it : net_lines) { + j["net_lines"][(std::string)it.first] = it.second.serialize(); + } + j["texts"] = json::object(); + for(const auto &it : texts) { + j["texts"][(std::string)it.first] = it.second.serialize(); + } + j["net_labels"] = json::object(); + for(const auto &it : net_labels) { + j["net_labels"][(std::string)it.first] = it.second.serialize(); + } + j["power_symbols"] = json::object(); + for(const auto &it : power_symbols) { + j["power_symbols"][(std::string)it.first] = it.second.serialize(); + } + j["bus_labels"] = json::object(); + for(const auto &it : bus_labels) { + j["bus_labels"][(std::string)it.first] = it.second.serialize(); + } + j["bus_rippers"] = json::object(); + for(const auto &it : bus_rippers) { + j["bus_rippers"][(std::string)it.first] = it.second.serialize(); + } + + return j; + } +} diff --git a/schematic/sheet.hpp b/schematic/sheet.hpp new file mode 100644 index 000000000..1acedb9de --- /dev/null +++ b/schematic/sheet.hpp @@ -0,0 +1,74 @@ +#pragma once +#include "uuid.hpp" +#include "json_fwd.hpp" +#include "object.hpp" +#include "unit.hpp" +#include "block.hpp" +#include "schematic_symbol.hpp" +#include "line_net.hpp" +#include "text.hpp" +#include "net_label.hpp" +#include "bus_label.hpp" +#include "bus_ripper.hpp" +#include "power_symbol.hpp" +#include "frame.hpp" +#include "warning.hpp" +#include +#include +#include + +namespace horizon { + using json = nlohmann::json; + + class NetSegmentInfo { + public: + NetSegmentInfo(LineNet *li); + NetSegmentInfo(Junction *ju); + bool has_label = false; + bool has_power_sym = false; + Coordi position; + Net *net = nullptr; + Bus *bus= nullptr; + bool is_bus() const; + }; + + class Sheet { + public : + Sheet(const UUID &uu, const json &, Block &Block, Object &pool); + Sheet(const UUID &uu); + UUID uuid; + std::string name; + unsigned int index; + + std::map junctions; + std::map symbols; + //std::map junction_pins; + std::map net_lines; + std::map texts; + std::map net_labels; + std::map power_symbols; + std::map bus_labels; + std::map bus_rippers; + std::vector warnings; + + LineNet *split_line_net(LineNet *it, Junction *ju); + void merge_net_lines(LineNet *a, LineNet *b, Junction *ju); + void expand_symbols(); + void simplify_net_lines(); + void delete_dependants(); + void propagate_net_segments(); + std::map analyze_net_segments(bool place_warnings=false); + std::set> get_pins_connected_to_net_segment(const UUID &uu_segment); + + void replace_junction(Junction *j, SchematicSymbol *sym, SymbolPin *pin); + Junction *replace_bus_ripper(BusRipper *rip); + //void replace_junction(Junction *j, PowerSymbol *sym); + //void replace_power_symbol(PowerSymbol *sym, Junction *j); + //void connect(SchematicSymbol *sym, SymbolPin *pin, PowerSymbol *power_sym); + + Frame frame; + + json serialize() const; + }; + +} diff --git a/util/accumulator.hpp b/util/accumulator.hpp new file mode 100644 index 000000000..e1b2f71c3 --- /dev/null +++ b/util/accumulator.hpp @@ -0,0 +1,21 @@ +#pragma once + +namespace horizon { + template class Accumulator { + public: + Accumulator(){}; + void accumulate(const T &v) { + value = (value*n+v)/(n+1); + n++; + } + T get() { + return value; + } + size_t get_n() { + return n; + } + private: + T value; + size_t n = 0; + }; +} diff --git a/util/placement.cpp b/util/placement.cpp new file mode 100644 index 000000000..7ad4ef903 --- /dev/null +++ b/util/placement.cpp @@ -0,0 +1,61 @@ +#include "placement.hpp" +#include "json.hpp" + +namespace horizon { + Placement::Placement(const json &j): + shift(j.at("shift").get>()), + angle(j.at("angle").get()), + mirror(j.at("mirror").get()) + { + } + json Placement::serialize() const { + json j; + j["shift"] = shift.as_array(); + j["angle"] = angle; + j["mirror"] = mirror; + return j; + } + + void Placement::accumulate(const Placement &p) { + Placement q = p; + + if(angle == 0) { + //nop + } + if(angle==16384) { + q.shift.y = p.shift.x; + q.shift.x = -p.shift.y; + } + else if(angle==32768) { + q.shift.y = -p.shift.y; + q.shift.x = -p.shift.x; + } + else if(angle==49152) { + q.shift.y = -p.shift.x; + q.shift.x = p.shift.y; + } + else { + double af = (angle/65536.0)*2*M_PI; + q.shift.x = p.shift.x*cos(af)-p.shift.y*sin(af); + q.shift.y = p.shift.x*sin(af)+p.shift.y*cos(af); + } + + if(mirror) { + q.shift.x = -q.shift.x; + } + + shift += q.shift; + angle += p.angle; + while(angle<0) { + angle+=65536; + } + angle %= 65536; + } + + void Placement::invert_angle() { + angle *= -1; + while(angle<0) + angle += 65536; + } + +} diff --git a/util/placement.hpp b/util/placement.hpp new file mode 100644 index 000000000..7ea4a6b30 --- /dev/null +++ b/util/placement.hpp @@ -0,0 +1,55 @@ +#pragma once +#include "common.hpp" +#include "json_fwd.hpp" + +namespace horizon { + using json = nlohmann::json; + + class Placement { + public : + Placement(const Coordi &sh={0,0}, int a=0, bool m = false):shift(sh), angle(a), mirror(m){} + Placement(const json &j); + templateCoord transform(const Coord &c) const { + Coord r = c; + int a = angle; + while(a<0) { + a+=65536; + } + if(angle == 0) { + //nop + } + if(angle==16384) { + r.y = c.x; + r.x = -c.y; + } + else if(angle==32768) { + r.y = -c.y; + r.x = -c.x; + } + else if(angle==49152) { + r.y = -c.x; + r.x = c.y; + } + else { + double af = (angle/65536.0)*2*M_PI; + r.x = c.x*cos(af)-c.y*sin(af); + r.y = c.x*sin(af)+c.y*cos(af); + } + if(mirror) { + r.x = -r.x; + } + + r += shift; + return r; + } + void angle_from_deg(int a) {angle = (a*65536)/360;} + void reset() {shift={0,0}, angle=0, mirror=false;} + void accumulate(const Placement &p); + void invert_angle(); + Coordi shift; + int angle = 0; + bool mirror = false; + json serialize() const; + }; + +} diff --git a/util/placement_provider.hpp b/util/placement_provider.hpp new file mode 100644 index 000000000..dc37da6c4 --- /dev/null +++ b/util/placement_provider.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "placement.hpp" + +namespace horizon { + class PlacementProvider { + public: + virtual Placement get_placement() const = 0; + virtual ~PlacementProvider() {} + }; +} diff --git a/util/position_provider.hpp b/util/position_provider.hpp new file mode 100644 index 000000000..b59368d4b --- /dev/null +++ b/util/position_provider.hpp @@ -0,0 +1,12 @@ +#pragma once +#include "common.hpp" +#include "uuid_provider.hpp" + +namespace horizon { + class PositionProvider: public UUIDProvider { + public: + virtual Coordi get_position() const = 0; + virtual ~PositionProvider() {} + }; + +} diff --git a/util/sort_controller.cpp b/util/sort_controller.cpp new file mode 100644 index 000000000..93ee8c837 --- /dev/null +++ b/util/sort_controller.cpp @@ -0,0 +1,93 @@ +#include "sort_controller.hpp" +#include + +namespace horizon { + SortController::SortController(Gtk::TreeView *tv):treeview(tv) { + tv->set_headers_clickable(); + } + + void SortController::add_column(unsigned int index, const std::string &name) { + columns.insert({index, {name, Sort::NONE}}); + treeview->get_column(index)->signal_clicked().connect(sigc::bind(sigc::mem_fun(this, &SortController::handle_click), index)); + update_treeview(); + } + + void SortController::set_simple(bool s) { + is_simple = s; + update_treeview(); + } + + void SortController::update_treeview() { + for(const auto &it: columns) { + treeview->get_column(it.first)->set_sort_indicator(it.second.second!=Sort::NONE); + treeview->get_column(it.first)->set_sort_order(it.second.second==Sort::ASC?Gtk::SORT_ASCENDING:Gtk::SORT_DESCENDING); + } + } + + void SortController::handle_click(unsigned int index) { + for(auto &it: columns) { + auto &c = it.second; + if(it.first == index) { + if(is_simple) { + switch(c.second) { + case Sort::ASC: c.second = Sort::DESC; break; + case Sort::DESC: c.second = Sort::ASC; break; + case Sort::NONE: c.second = Sort::ASC; break; + } + } + else { + switch(c.second) { + case Sort::ASC: c.second = Sort::DESC; break; + case Sort::DESC: c.second = Sort::NONE; break; + case Sort::NONE: c.second = Sort::ASC; break; + } + } + } + else if(is_simple){ + c.second = Sort::NONE; + } + } + + update_treeview(); + s_signal_changed.emit(); + } + + void SortController::set_sort(unsigned int index, Sort s) { + for(auto &it: columns) { + auto &c = it.second; + if(it.first == index) { + c.second = s; + } + else if(is_simple){ + c.second = Sort::NONE; + } + } + + update_treeview(); + s_signal_changed.emit(); + } + + std::string SortController::get_order_by() const { + if(is_simple) { + auto x = std::find_if(columns.cbegin(), columns.cend(), [](const auto &a){return a.second.second != Sort::NONE;}); + if(x == columns.cend()) { + return ""; + } + else { + std::stringstream ss; + ss << "ORDER BY " << x->second.first << " "; + if(x->second.second == Sort::ASC) { + ss << "ASC"; + } + else { + ss << "DESC"; + } + return ss.str(); + } + } + else { + return ""; + } + } + +} diff --git a/util/sort_controller.hpp b/util/sort_controller.hpp new file mode 100644 index 000000000..2e33283f3 --- /dev/null +++ b/util/sort_controller.hpp @@ -0,0 +1,28 @@ +#pragma once +#include + +namespace horizon { + class SortController :public sigc::trackable{ + public: + enum class Sort {ASC, DESC, NONE}; + + SortController(Gtk::TreeView *tv); + void add_column(unsigned int index, const std::string &name); + void set_simple(bool s); + std::string get_order_by() const; + void set_sort(unsigned int index, Sort s); + + + typedef sigc::signal type_signal_changed; + type_signal_changed signal_changed() {return s_signal_changed;} + + private: + Gtk::TreeView *treeview; + std::map> columns; + void update_treeview(); + void handle_click(unsigned int index); + bool is_simple; + + type_signal_changed s_signal_changed; + }; +} diff --git a/util/sqlite.cpp b/util/sqlite.cpp new file mode 100644 index 000000000..b2b84fa89 --- /dev/null +++ b/util/sqlite.cpp @@ -0,0 +1,90 @@ +#include "sqlite.hpp" + + +namespace SQLite { + Database::Database(const std::string &filename, int flags) { + if(sqlite3_open_v2(filename.c_str(), &db, flags, nullptr) != SQLITE_OK) { + throw std::runtime_error(sqlite3_errmsg(db)); + } + } + + void Database::execute(const std::string &query) { + execute(query.c_str()); + } + + void Database::execute(const char *query) { + if(sqlite3_exec(db, query, nullptr, nullptr, nullptr) != SQLITE_OK) { + throw std::runtime_error(sqlite3_errmsg(db)); + } + } + + Query::Query(Database &dab, const std::string &sql) : db(dab) { + if(sqlite3_prepare_v2(db.db, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { + throw std::runtime_error(sqlite3_errmsg(db.db)); + } + } + Query::Query(Database &dab, const char *sql, int size) : db(dab) { + if(sqlite3_prepare_v2(db.db, sql, size, &stmt, nullptr) != SQLITE_OK) { + throw std::runtime_error(sqlite3_errmsg(db.db)); + } + } + + Query::~Query() { + sqlite3_finalize(stmt); + stmt = nullptr; + } + + bool Query::step() { + auto rc = sqlite3_step(stmt); + if(rc == SQLITE_ERROR) { + throw std::runtime_error(sqlite3_errmsg(db.db)); + } + return rc == SQLITE_ROW; + } + + std::string Query::get(int idx, std::string) const { + auto c = reinterpret_cast(sqlite3_column_text(stmt, idx));\ + if(c) { + return c; + } + else { + return ""; + } + } + + int Query::get(int idx, int) const { + return sqlite3_column_int(stmt, idx); + } + + + void Query::bind(int idx, const std::string &v, bool copy) { + if(sqlite3_bind_text(stmt, idx, v.c_str(), -1, copy?SQLITE_TRANSIENT:SQLITE_STATIC) != SQLITE_OK) { + throw std::runtime_error(sqlite3_errmsg(db.db)); + } + } + void Query::bind(const char *name, const std::string &v, bool copy) { + bind(sqlite3_bind_parameter_index(stmt, name), v, copy); + } + + void Query::bind(int idx, int v) { + if(sqlite3_bind_int(stmt, idx, v) != SQLITE_OK) { + throw std::runtime_error(sqlite3_errmsg(db.db)); + } + } + void Query::bind(const char *name, int v) { + bind(sqlite3_bind_parameter_index(stmt, name), v); + } + + void Query::bind(int idx, const horizon::UUID &v) { + bind(idx, (std::string)v); + } + void Query::bind(const char *name, const horizon::UUID &v) { + bind(name, (std::string)v); + } + + Database::~Database() { + sqlite3_close_v2(db); + } + + +} diff --git a/util/sqlite.hpp b/util/sqlite.hpp new file mode 100644 index 000000000..1979f0e10 --- /dev/null +++ b/util/sqlite.hpp @@ -0,0 +1,70 @@ +#pragma once +#include +#include +#include +#include "uuid.hpp" + +namespace SQLite { + class noncopyable{ + protected: + noncopyable() = default; + ~noncopyable() = default; + + noncopyable(noncopyable&&) = default; + noncopyable& operator=(noncopyable&&) = default; + + noncopyable(noncopyable const&) = delete; + noncopyable& operator=(noncopyable const&) = delete; + }; + + class Query : noncopyable{ + public: + Query(class Database &d, const std::string &sql); + Query(class Database &d, const char *sql, int size=-1); + ~Query (); + bool step(); + template T get(int idx) const { + return get(idx, T()); + } + + template + struct convert { + using to_int = int; + }; + + template + std::tuple get_columns(typename convert::to_int... idxs) const { + return std::make_tuple(get(idxs, Ts())...); + } + + void bind(int idx, const std::string &v, bool copy=true); + void bind(const char *name, const std::string &v, bool copy=true); + void bind(int idx, int v); + void bind(const char *name, int v); + void bind(int idx, const horizon::UUID &v); + void bind(const char *name, const horizon::UUID &v); + + private: + class Database &db; + sqlite3_stmt *stmt; + + + std::string get(int idx, std::string) const; + int get(int idx, int) const; + }; + + + class Database { + friend Query; + public: + Database(const std::string &filename, int flags=SQLITE_OPEN_READONLY); + ~Database(); + void execute(const std::string &query); + void execute(const char *query); + + + private: + sqlite3 *db = nullptr; + + }; +} diff --git a/util/util.cpp b/util/util.cpp new file mode 100644 index 000000000..44841da61 --- /dev/null +++ b/util/util.cpp @@ -0,0 +1,16 @@ +#include "util.hpp" +#include "json.hpp" +#include + + +namespace horizon { + void save_json_to_file(const std::string &filename, const json &j) { + std::ofstream ofs(filename); + if(!ofs.is_open()) { + std::cout << "can't save json " << filename < (const UUID &self, const UUID &other) { + return other < self; + } +}; diff --git a/util/uuid.hpp b/util/uuid.hpp new file mode 100644 index 000000000..b0c744463 --- /dev/null +++ b/util/uuid.hpp @@ -0,0 +1,34 @@ +#pragma once +#ifdef WIN32_UUID + #include "uuid_win32.hpp" +#else + #include +#endif + +#include + +namespace horizon { + class UUID { + public : + UUID(); + static UUID random(); + UUID(const char *str); + UUID(const std::string &str); + operator std::string() const { + char str[40]; + uuid_unparse(uu, str); + return std::string(str); + } + + operator bool() const { + return !uuid_is_null(uu); + } + + friend bool operator== (const UUID &self, const UUID &other); + friend bool operator!= (const UUID &self, const UUID &other); + friend bool operator< (const UUID &self, const UUID &other); + friend bool operator> (const UUID &self, const UUID &other); + private : + uuid_t uu; + }; +} diff --git a/util/uuid_path.cpp b/util/uuid_path.cpp new file mode 100644 index 000000000..7009edef6 --- /dev/null +++ b/util/uuid_path.cpp @@ -0,0 +1,5 @@ +#include "uuid_path.hpp" + +namespace horizon { + +} diff --git a/util/uuid_path.hpp b/util/uuid_path.hpp new file mode 100644 index 000000000..30d4e96af --- /dev/null +++ b/util/uuid_path.hpp @@ -0,0 +1,57 @@ +#pragma once +#include "uuid.hpp" +#include +#include + +namespace horizon { + + template class UUIDPath { + public : + UUIDPath() {} + UUIDPath(const UUID &uu):path({uu, uu}) {} + UUIDPath(const UUID &uu0, const UUID &uu1):path({uu0, uu1}) {} + UUIDPath(const UUID &uu0, const UUID &uu1, const UUID &uu2):path({uu0, uu1, uu2}) {} + UUIDPath(const std::string &str) { + if(N==1) { + path[0] = str; + } + if(N==2) { + path[0] = str.substr(0, 36); + path[1] = str.substr(37, 36); + } + } + operator std::string() const { + if(N==1) { + return path[0]; + }if(N==2) { + return (std::string)path[0]+ "/" + (std::string)path[1]; + } + } + bool operator<(const UUIDPath &other) const { + for(unsigned int i(0);iother.path[i]) { + return false; + } + } + return false; + } + bool operator==(const UUIDPath &other) const { + for(unsigned int i(0);i path; + }; +} diff --git a/util/uuid_provider.hpp b/util/uuid_provider.hpp new file mode 100644 index 000000000..a3158743f --- /dev/null +++ b/util/uuid_provider.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "uuid.hpp" + +namespace horizon { + class UUIDProvider { + public: + virtual UUID get_uuid() const = 0; + virtual ~UUIDProvider() {} + }; +} diff --git a/util/uuid_ptr.hpp b/util/uuid_ptr.hpp new file mode 100644 index 000000000..5edaabeb8 --- /dev/null +++ b/util/uuid_ptr.hpp @@ -0,0 +1,55 @@ +#pragma once +#include "uuid.hpp" +#include "uuid_provider.hpp" +#include +#include +#include + +namespace horizon { + template class uuid_ptr { + public : + uuid_ptr():ptr(nullptr) {} + uuid_ptr(const UUID &uu):ptr(nullptr), uuid(uu) {} + uuid_ptr(T *p, const UUID &uu):ptr(p), uuid(uu) {} + uuid_ptr(T *p): ptr(p), uuid(p?p->get_uuid():UUID()) { + /* static_assert( + std::is_base_of::value, + "T must be a descendant of MyBase" + );*/ + } + T& operator* () { + if(ptr) { + assert(ptr->get_uuid()==uuid); + } + return *ptr; + } + + T* operator-> () const { + if(ptr) { + assert(ptr->get_uuid()==uuid); + } + return ptr; + } + + operator T*() const { + if(ptr) { + assert(ptr->get_uuid()==uuid); + } + return ptr; + } + + T *ptr; + UUID uuid; + void update(std::map &map) { + if(uuid) { + if(map.count(uuid)) { + ptr = &map.at(uuid); + } + else { + ptr = nullptr; + } + } + } + }; + +} diff --git a/util/uuid_win32.cpp b/util/uuid_win32.cpp new file mode 100644 index 000000000..26021af9e --- /dev/null +++ b/util/uuid_win32.cpp @@ -0,0 +1,69 @@ +#include +#include + +typedef unsigned char wuuid_t[16]; + +void uuid_clear(wuuid_t uu) { + memset(uu, 0, 16); +} + +static void rpc_uuid_to_uuid_t(const UUID *uu, wuuid_t out) { + out[0] = (uu->Data1>>24)&0xff; + out[1] = (uu->Data1>>16)&0xff; + out[2] = (uu->Data1>> 8)&0xff; + out[3] = (uu->Data1>> 0)&0xff; + out[4] = (uu->Data2>>8)&0xff; + out[5] = (uu->Data2>>0)&0xff; + out[6] = (uu->Data3>>8)&0xff; + out[7] = (uu->Data3>>0)&0xff; + memcpy(out+8, uu->Data4, 8); +} + +static void uuid_t_to_rpc_uuid(const wuuid_t in, UUID *out) { + out->Data1 = (in[0]<<24) | (in[1]<<16) | (in[2]<<8) | (in[3]<<0); + out->Data2 = (in[4]<<8) | (in[5]<<0); + out->Data3 = (in[6]<<8) | (in[7]<<0); + memcpy(out->Data4, in+8, 8); +} + +void uuid_generate_random(wuuid_t out) { + UUID uu; + UuidCreate(&uu); + rpc_uuid_to_uuid_t(&uu, out); +} +void uuid_unparse(const wuuid_t uu, char *out) { + sprintf(out, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + uu[0], uu[1], uu[2], uu[3], uu[4], uu[5], uu[6], uu[7], uu[8], uu[9], uu[10], uu[11], uu[12], uu[13], uu[14], uu[15]); +} + +int uuid_parse(const char *in, wuuid_t out) { + UUID uu; + unsigned char c[37]; + memcpy(c, in, 37); + RPC_STATUS ret = UuidFromString((unsigned char *)c, &uu); + if(ret == RPC_S_OK) { + rpc_uuid_to_uuid_t(&uu, out); + return 0; + } + else { + return -1; + } +} + +int uuid_compare(const wuuid_t uu1, const wuuid_t uu2) { + RPC_STATUS stat; + UUID u1, u2; + uuid_t_to_rpc_uuid(uu1, &u1); + uuid_t_to_rpc_uuid(uu2, &u2); + return UuidCompare(&u1, &u2, &stat); +} + +int uuid_is_null(const wuuid_t uu) { + const unsigned char *cp; + int i; + + for (i=0, cp = uu; i < 16; i++) + if (*cp++) + return 0; + return 1; +} diff --git a/util/uuid_win32.hpp b/util/uuid_win32.hpp new file mode 100644 index 000000000..4254fc799 --- /dev/null +++ b/util/uuid_win32.hpp @@ -0,0 +1,9 @@ +#pragma once +typedef unsigned char uuid_t[16]; + +extern void uuid_clear(uuid_t uu); +extern void uuid_generate_random(uuid_t out); +extern void uuid_unparse(const uuid_t uu, char *out); +extern int uuid_parse(const char *in, uuid_t uu); +extern int uuid_compare(const uuid_t uu1, const uuid_t uu2); +extern int uuid_is_null(const uuid_t uu); \ No newline at end of file diff --git a/util/warning.hpp b/util/warning.hpp new file mode 100644 index 000000000..9141ca2b1 --- /dev/null +++ b/util/warning.hpp @@ -0,0 +1,13 @@ +#pragma once +#include +#include "common.hpp" + +namespace horizon { + class Warning { + public: + Warning(const Coordi &c, const std::string &t): position(c), text(t) {} + Coordi position; + std::string text; + }; + +} diff --git a/widgets/chooser_buttons.cpp b/widgets/chooser_buttons.cpp new file mode 100644 index 000000000..b6f3a0946 --- /dev/null +++ b/widgets/chooser_buttons.cpp @@ -0,0 +1,31 @@ +#include "chooser_buttons.hpp" +#include "dialogs/pool_browser_padstack.hpp" + +namespace horizon { + PadstackButton::PadstackButton(Pool &p, const UUID &pkg_uuid):Glib::ObjectBase (typeid(PadstackButton)), Gtk::Button("fixme"), p_property_selected_uuid(*this, "selected-uuid"), pool(p), package_uuid(pkg_uuid) { + update_label(); + property_selected_uuid().signal_changed().connect([this]{update_label();}); + } + + void PadstackButton::on_clicked() { + Gtk::Button::on_clicked(); + auto top = dynamic_cast(get_ancestor(GTK_TYPE_WINDOW)); + PoolBrowserDialogPadstack pb(top, &pool, package_uuid); + pb.run(); + if(pb.selection_valid) { + p_property_selected_uuid = pb.selected_uuid; + update_label(); + } + } + + void PadstackButton::update_label() { + UUID uu = p_property_selected_uuid; + if(uu) { + set_label(pool.get_padstack(uu)->name); + } + else { + set_label("no padstack"); + } + } + +} diff --git a/widgets/chooser_buttons.hpp b/widgets/chooser_buttons.hpp new file mode 100644 index 000000000..745648bc2 --- /dev/null +++ b/widgets/chooser_buttons.hpp @@ -0,0 +1,22 @@ +#pragma once +#include +#include "uuid.hpp" +#include "pool.hpp" + +namespace horizon { + class PadstackButton: public Gtk::Button { + public: + PadstackButton(Pool &p, const UUID &pkg_uuid); + Glib::PropertyProxy property_selected_uuid() { return p_property_selected_uuid.get_proxy(); } + + protected : + Glib::Property p_property_selected_uuid; + Pool &pool; + UUID package_uuid; + + void on_clicked() override; + void update_label(); + }; + + +} diff --git a/widgets/layer_box.cpp b/widgets/layer_box.cpp new file mode 100644 index 000000000..487779afc --- /dev/null +++ b/widgets/layer_box.cpp @@ -0,0 +1,353 @@ +#include "layer_box.hpp" +#include "json.hpp" +#include +#include + +namespace horizon { + + + class MyCellRenderer: public Gtk::CellRenderer { + public: + MyCellRenderer(); + virtual ~MyCellRenderer() {}; + + typedef Glib::Property type_property_color; + Glib::PropertyProxy property_color() { return p_property_color.get_proxy(); } + typedef Glib::Property type_property_display_mode; + Glib::PropertyProxy property_display_mode() { return p_property_display_mode.get_proxy(); } + typedef sigc::signal type_signal_activate; + type_signal_activate signal_activate() {return s_signal_activate;} + + + protected: + + + virtual void render_vfunc( const Cairo::RefPtr& cr, + Gtk::Widget& widget, + const Gdk::Rectangle& background_area, + const Gdk::Rectangle& cell_area, + Gtk::CellRendererState flags ); + + virtual void get_preferred_width_vfunc(Gtk::Widget& widget, + int& min_w, + int& nat_w) const; + + virtual void get_preferred_height_vfunc(Gtk::Widget& widget, + int& min_h, + int& nat_h) const; + + virtual bool activate_vfunc(GdkEvent *event, + Gtk::Widget &widget, + const Glib::ustring &path, + const Gdk::Rectangle &background_area, + const Gdk::Rectangle &cell_area, + Gtk::CellRendererState flags); + + private: + type_property_color p_property_color; + type_property_display_mode p_property_display_mode; + type_signal_activate s_signal_activate; + }; + + MyCellRenderer::MyCellRenderer(): Glib::ObjectBase(typeid(MyCellRenderer)), + Gtk::CellRenderer(), + p_property_color(*this, "color"), + p_property_display_mode(*this, "display-mode") { + property_mode() = Gtk::CELL_RENDERER_MODE_ACTIVATABLE; + } + + void MyCellRenderer::get_preferred_height_vfunc(Gtk::Widget& widget, + int& min_h, + int& nat_h) const + { + min_h = nat_h = 16; + } + + void MyCellRenderer::get_preferred_width_vfunc(Gtk::Widget& widget, + int& min_w, + int& nat_w) const + { + min_w = nat_w = 16; + } + + void MyCellRenderer::render_vfunc( const Cairo::RefPtr& cr, + Gtk::Widget& widget, + const Gdk::Rectangle& background_area, + const Gdk::Rectangle& cell_area, + Gtk::CellRendererState flags ) + { + cr->save(); + const auto c = p_property_color.get_value(); + + const unsigned int d = 16; + cr->translate(cell_area.get_x()+(cell_area.get_width()-d)/2, cell_area.get_y()+(cell_area.get_height()-d)/2); + cr->rectangle(0, 0, 16, 16); + cr->set_source_rgb(0,0,0); + cr->fill_preserve(); + cr->set_source_rgb(c.get_red(), c.get_green(), c.get_blue()); + cr->set_line_width(2); + const auto dm = p_property_display_mode.get_value(); + if(dm == 0) { + cr->fill_preserve(); + } + + cr->stroke(); + + if(dm == 1) { + cr->move_to(0,16); + cr->line_to(16,0); + cr->stroke(); + cr->move_to(0,9); + cr->line_to(9,0); + cr->stroke(); + cr->move_to(7,16); + cr->line_to(16,7); + cr->stroke(); + } + + cr->restore(); + + } + + bool MyCellRenderer::activate_vfunc(GdkEvent *event, + Gtk::Widget &widget, + const Glib::ustring &path, + const Gdk::Rectangle &background_area, + const Gdk::Rectangle &cell_area, + Gtk::CellRendererState flags) + { + s_signal_activate.emit(path); + return false; + } + + + LayerBox::LayerBox(Core *c): + Glib::ObjectBase(typeid(LayerBox)), + Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL, 2), + core(c), + p_property_work_layer(*this, "work-layer"), + p_property_select_work_layer_only(*this, "select-work-layer-only"), + p_property_layer_opacity(*this, "layer-opacity") + { + auto *la = Gtk::manage(new Gtk::Label()); + la->set_markup("Layers"); + la->show(); + pack_start(*la, false, false, 0); + + store = Gtk::ListStore::create(list_columns); + store->set_sort_column(list_columns.index, Gtk::SORT_DESCENDING); + + auto fr = Gtk::manage(new Gtk::Frame()); + fr->set_shadow_type(Gtk::SHADOW_IN); + auto frb = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0)); + fr->add(*frb); + + view = Gtk::manage(new Gtk::TreeView(store)); + view->get_selection()->set_mode(Gtk::SELECTION_NONE); + property_work_layer().signal_changed().connect(sigc::mem_fun(this, &LayerBox::update_work_layer)); + p_property_work_layer.set_value(0); + + + { + auto cr = Gtk::manage(new Gtk::CellRendererToggle()); + cr->signal_toggled().connect(sigc::mem_fun(this, &LayerBox::work_toggled)); + cr->set_radio(true); + auto tvc = Gtk::manage(new Gtk::TreeViewColumn("W", *cr)); + tvc->add_attribute(cr->property_active(), list_columns.is_work); + cr->property_xalign().set_value(1); + view->append_column(*tvc); + } + { + auto cr = Gtk::manage(new Gtk::CellRendererToggle()); + cr->signal_toggled().connect(sigc::mem_fun(this, &LayerBox::toggled)); + auto tvc = Gtk::manage(new Gtk::TreeViewColumn("V", *cr)); + //tvc->add_attribute(cr->property_active(), list_columns.visible); + tvc->set_cell_data_func(*cr, sigc::mem_fun(this, &LayerBox::visible_cell_data_func)); + cr->property_xalign().set_value(1); + view->append_column(*tvc); + } + { + auto cr = Gtk::manage(new MyCellRenderer()); + cr->signal_activate().connect(sigc::mem_fun(this, &LayerBox::activated)); + auto tvc = Gtk::manage(new Gtk::TreeViewColumn("D", *cr)); + tvc->add_attribute(cr->property_color(), list_columns.color); + tvc->add_attribute(cr->property_display_mode(), list_columns.display_mode); + //tvc->add_attribute(*cr, "active", list_columns.visible); + //cr->property_xalign().set_value(1); + view->append_column(*tvc); + } + { + auto cr = Gtk::manage(new Gtk::CellRendererText()); + auto tvc = Gtk::manage(new Gtk::TreeViewColumn("I", *cr)); + tvc->add_attribute(cr->property_text(), list_columns.index); + cr->property_xalign().set_value(1); + view->append_column(*tvc); + } + //view->append_column("", list_columns.index); + view->append_column("Name", list_columns.name); + + view->show(); + auto sc = Gtk::manage(new Gtk::ScrolledWindow()); + sc->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC); + //sc->set_shadow_type(Gtk::SHADOW_IN); + sc->set_min_content_height(300); + sc->add(*view); + sc->show_all(); + frb->pack_start(*sc, true, true, 0); + + auto ab = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL, 2)); + ab->set_homogeneous(true); + ab->set_margin_top(4); + ab->set_margin_bottom(4); + ab->set_margin_start(4); + ab->set_margin_end(4); + + auto sel_only_work_layer_tb = Gtk::manage(new Gtk::CheckButton("Select only on work layer")); + binding_select_work_layer_only = Glib::Binding::bind_property(sel_only_work_layer_tb->property_active(), property_select_work_layer_only(), Glib::BINDING_BIDIRECTIONAL); + property_select_work_layer_only() = false; + ab->pack_start(*sel_only_work_layer_tb); + + auto layer_opacity_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4)); + layer_opacity_box->set_margin_start(4); + auto la2 = Gtk::manage(new Gtk::Label("Layer Opacity")); + layer_opacity_box->pack_start(*la2, false, false, 0); + + auto adj = Gtk::Adjustment::create(90.0, 10.0, 100.0, 1.0, 10.0, 0.0); + binding_layer_opacity = Glib::Binding::bind_property(adj->property_value(), property_layer_opacity(), Glib::BINDING_BIDIRECTIONAL); + + auto layer_opacity_scale = Gtk::manage(new Gtk::Scale(adj, Gtk::ORIENTATION_HORIZONTAL)); + layer_opacity_scale->set_digits(0); + layer_opacity_scale->set_value_pos(Gtk::POS_LEFT); + layer_opacity_box->pack_start(*layer_opacity_scale, true, true, 0); + + + ab->pack_start(*layer_opacity_box); + + frb->show_all(); + frb->pack_start(*ab, false, false, 0); + pack_start(*fr, true, true, 0); + + + + update(); + } + + + void LayerBox::visible_cell_data_func(Gtk::CellRenderer *cr, const Gtk::TreeModel::iterator &it) { + auto crt = dynamic_cast(cr); + Gtk::TreeModel::Row row = *it; + if(row[list_columns.is_work]) { + crt->property_active() = true; + crt->property_sensitive() = false; + } + else { + crt->property_sensitive() = true; + crt->property_active() = row[list_columns.visible]; + } + } + + void LayerBox::emit_layer_display(const Gtk::TreeModel::Row &row) { + const Gdk::RGBA &co = row[list_columns.color]; + Color c(co.get_red(), co.get_green(),co.get_blue()); + LayerDisplay::Mode dm; + switch(row[list_columns.display_mode]) { + case 0: dm=LayerDisplay::Mode::FILL; break; + case 1: dm=LayerDisplay::Mode::HATCH; break; + default: dm=LayerDisplay::Mode::OUTLINE; + } + s_signal_set_layer_display.emit(row[list_columns.index], LayerDisplay(row[list_columns.visible], dm, c)); + } + + + void LayerBox::toggled(const Glib::ustring& path) { + auto it = store->get_iter(path); + if(it) { + Gtk::TreeModel::Row row = *it; + row[list_columns.visible] = !row[list_columns.visible]; + emit_layer_display(row); + } + } + void LayerBox::work_toggled(const Glib::ustring& path) { + for(auto &it: store->children()) { + it[list_columns.is_work] = false; + } + + auto it = store->get_iter(path); + if(it) { + Gtk::TreeModel::Row row = *it; + row[list_columns.is_work] = true; + property_work_layer().set_value(row[list_columns.index]); + } + } + + void LayerBox::activated(const Glib::ustring& path) { + auto it = store->get_iter(path); + if(it) { + Gtk::TreeModel::Row row = *it; + row[list_columns.display_mode] = (row[list_columns.display_mode]+1)%3; + emit_layer_display(row); + } + } + + + + void LayerBox::update() { + auto layers = core->get_layers(); + Gtk::TreeModel::Row row; + store->freeze_notify(); + store->clear(); + for(const auto &it: layers) { + row = *(store->append()); + + row[list_columns.name] = it.second.name; + row[list_columns.index] = it.first; + row[list_columns.is_work] = (it.first==p_property_work_layer); + row[list_columns.visible] = true; + const auto & co = it.second.color; + auto c = Gdk::RGBA(); + c.set_rgba(co.r, co.g, co.b); + row[list_columns.color] = c; + row[list_columns.display_mode] = 0; + } + store->thaw_notify(); + } + void LayerBox::update_work_layer() { + auto layers = core->get_layers(); + Gtk::TreeModel::Row row; + store->freeze_notify(); + for(auto &it: store->children()) { + it[list_columns.is_work] = (it[list_columns.index]==p_property_work_layer); + } + store->thaw_notify(); + } + + json LayerBox::serialize() { + json j; + j["layer_opacity"] = property_layer_opacity().get_value(); + for(auto &it: store->children()) { + json k; + k["visible"] = static_cast(it[list_columns.visible]); + k["display_mode"] = static_cast(it[list_columns.display_mode]); + int index = it[list_columns.index]; + j["layers"][std::to_string(index)] = k; + } + return j; + } + + void LayerBox::load_from_json(const json &j) { + if(j.count("layers")) { + property_layer_opacity() = j.value("layer_opacity", 90); + const auto &j2 = j.at("layers"); + for(auto &it: store->children()) { + std::string index_str = std::to_string(it[list_columns.index]); + if(j2.count(index_str)) { + auto &k = j2.at(index_str); + it[list_columns.visible] = static_cast(k["visible"]); + it[list_columns.display_mode] = static_cast(k["display_mode"]); + emit_layer_display(it); + } + } + } + } + +} diff --git a/widgets/layer_box.hpp b/widgets/layer_box.hpp new file mode 100644 index 000000000..755d03e7c --- /dev/null +++ b/widgets/layer_box.hpp @@ -0,0 +1,68 @@ +#pragma once +#include +#include "core/core.hpp" +#include "sheet.hpp" +#include "canvas/layer_display.hpp" + +namespace horizon { + class LayerBox: public Gtk::Box { + public: + LayerBox(Core *c); + + void update(); + Glib::PropertyProxy property_work_layer() { return p_property_work_layer.get_proxy(); } + typedef sigc::signal type_signal_set_layer_display; + type_signal_set_layer_display signal_set_layer_display() {return s_signal_set_layer_display;} + + + Glib::PropertyProxy property_layer_opacity() { return p_property_layer_opacity.get_proxy(); } + + Glib::PropertyProxy property_select_work_layer_only() { return p_property_select_work_layer_only.get_proxy(); } + json serialize(); + void load_from_json(const json &j); + + private: + class ListColumns : public Gtk::TreeModelColumnRecord { + public: + ListColumns() { + Gtk::TreeModelColumnRecord::add(name) ; + Gtk::TreeModelColumnRecord::add(index) ; + Gtk::TreeModelColumnRecord::add(visible) ; + Gtk::TreeModelColumnRecord::add(color) ; + Gtk::TreeModelColumnRecord::add(display_mode) ; + Gtk::TreeModelColumnRecord::add(is_work) ; + } + Gtk::TreeModelColumn name; + Gtk::TreeModelColumn index; + Gtk::TreeModelColumn visible; + Gtk::TreeModelColumn is_work; + Gtk::TreeModelColumn color; + Gtk::TreeModelColumn display_mode; + } ; + ListColumns list_columns; + + Core *core; + + Gtk::TreeView *view; + Glib::RefPtr store; + void toggled(const Glib::ustring& path); + void work_toggled(const Glib::ustring& path); + void activated(const Glib::ustring& path); + void selection_changed(); + Glib::Property p_property_work_layer; + Glib::Property p_property_select_work_layer_only; + Glib::Property p_property_layer_opacity; + type_signal_set_layer_display s_signal_set_layer_display; + void emit_layer_display(const Gtk::TreeModel::Row &row); + void update_work_layer(); + + Glib::RefPtr binding_select_work_layer_only; + Glib::RefPtr binding_layer_opacity; + void visible_cell_data_func(Gtk::CellRenderer *cr, const Gtk::TreeModel::iterator &it); + + + + }; + + +} diff --git a/widgets/net_button.cpp b/widgets/net_button.cpp new file mode 100644 index 000000000..dffd55992 --- /dev/null +++ b/widgets/net_button.cpp @@ -0,0 +1,51 @@ +#include "net_button.hpp" + +namespace horizon { + NetButton::NetButton(Block *b):Gtk::MenuButton(), block(b) { + popover = Gtk::manage(new Gtk::Popover(*this)); + ns = Gtk::manage(new NetSelector(block)); + ns->set_size_request(100, 200); + ns->signal_activated().connect(sigc::mem_fun(this, &NetButton::ns_activated)); + ns->show(); + + popover->add(*ns); + set_popover(*popover); + net_current = ns->get_selected_net(); + update_label(); + } + + void NetButton::on_toggled() { + ns->select_net(net_current); + Gtk::ToggleButton::on_toggled(); + } + + void NetButton::update() { + ns->update(); + on_toggled(); + update_label(); + } + + void NetButton::set_net(const UUID &uu) { + net_current = uu; + update_label(); + } + + void NetButton::update_label() { + if(net_current) { + set_label(block->nets.at(net_current).name); + } + else { + set_label(""); + } + } + + void NetButton::ns_activated(const UUID &uu) { + set_active(false); + net_current = uu; + s_signal_changed.emit(uu); + update_label(); + } + + + +} diff --git a/widgets/net_button.hpp b/widgets/net_button.hpp new file mode 100644 index 000000000..a126da818 --- /dev/null +++ b/widgets/net_button.hpp @@ -0,0 +1,29 @@ +#pragma once +#include +#include "block.hpp" +#include "net_selector.hpp" + +namespace horizon { + + class NetButton: public Gtk::MenuButton { + public: + NetButton(Block *b); + void set_net(const UUID &uu); + typedef sigc::signal type_signal_changed; + type_signal_changed signal_changed() {return s_signal_changed;} + void update(); + + private : + Block *block; + Gtk::Popover *popover; + NetSelector *ns; + void update_label(); + void ns_activated(const UUID &uu); + UUID net_current; + virtual void on_toggled(); + + type_signal_changed s_signal_changed; + + }; + +} diff --git a/widgets/net_selector.cpp b/widgets/net_selector.cpp new file mode 100644 index 000000000..07b73b3a3 --- /dev/null +++ b/widgets/net_selector.cpp @@ -0,0 +1,108 @@ +#include "net_selector.hpp" +#include +#include + +namespace horizon { + NetSelector::NetSelector(Block *b): + Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL, 16), + block(b) + { + + store = Gtk::ListStore::create(list_columns); + + + view = Gtk::manage(new Gtk::TreeView(store)); + view->get_selection()->set_mode(Gtk::SELECTION_BROWSE); + view->append_column("fixme", list_columns.name); + view->get_column(0)->set_sort_column(list_columns.name); + view->get_column(0)->set_sort_order(Gtk::SORT_ASCENDING); + view->get_column(0)->set_sort_indicator(true); + update(); + + view->signal_row_activated().connect(sigc::mem_fun(this, &NetSelector::row_activated)); + + auto sc = Gtk::manage(new Gtk::ScrolledWindow()); + sc->add(*view); + sc->show_all(); + + pack_start(*sc, true, true, 0); + } + + void NetSelector::update() { + + Gtk::TreeModel::Row row; + store->freeze_notify(); + store->clear(); + + if(bus_mode) { + view->get_column(0)->set_title("Bus"); + for(const auto &it: block->buses) { + row = *(store->append()); + row[list_columns.name] = it.second.name; + row[list_columns.uuid] = it.second.uuid; + } + } + else if(bus_member_mode) { + view->get_column(0)->set_title("Bus Member"); + for(const auto &it: bus->members) { + row = *(store->append()); + row[list_columns.name] = it.second.name; + row[list_columns.uuid] = it.second.uuid; + } + } + else { + view->get_column(0)->set_title("Net"); + for(const auto &it: block->nets) { + if(it.second.is_named() && (!power_only || it.second.is_power)) { + row = *(store->append()); + row[list_columns.name] = it.second.name; + row[list_columns.uuid] = it.second.uuid; + } + } + } + store->thaw_notify(); + } + + void NetSelector::set_power_only(bool p) { + power_only = p; + update(); + } + void NetSelector::set_bus_mode(bool b) { + bus_mode = b; + update(); + } + void NetSelector::set_bus_member_mode(const UUID &bus_uuid) { + bus_member_mode = true; + bus = &block->buses.at(bus_uuid); + update(); + } + + UUID NetSelector::get_selected_net() { + auto it = view->get_selection()->get_selected(); + if(it) { + Gtk::TreeModel::Row row = *it; + return row[list_columns.uuid]; + } + return UUID(); + } + + void NetSelector::row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column) { + auto it = store->get_iter(path); + if(it) { + Gtk::TreeModel::Row row = *it; + s_signal_activated.emit(row[list_columns.uuid]); + } + } + + void NetSelector::select_net(const UUID &uu) { + for(const auto &it: store->children()) { + Gtk::TreeModel::Row row = *it; + if(row[list_columns.uuid] == uu) { + view->get_selection()->select(it); + break; + } + } + } + + +} diff --git a/widgets/net_selector.hpp b/widgets/net_selector.hpp new file mode 100644 index 000000000..91c819258 --- /dev/null +++ b/widgets/net_selector.hpp @@ -0,0 +1,50 @@ +#pragma once +#include +#include "core/core.hpp" +#include "sheet.hpp" + +namespace horizon { + class NetSelector: public Gtk::Box { + public: + NetSelector(Block *b); + void set_power_only(bool p); + void set_bus_mode(bool b); + void set_bus_member_mode(const UUID &bus_uuid); + UUID get_selected_net(); + void select_net(const UUID &uu); + + typedef sigc::signal type_signal_selected; + //type_signal_selected signal_selected() {return s_signal_selected;} + type_signal_selected signal_activated() {return s_signal_activated;} + void update(); + + private: + class ListColumns : public Gtk::TreeModelColumnRecord { + public: + ListColumns() { + Gtk::TreeModelColumnRecord::add( name ) ; + Gtk::TreeModelColumnRecord::add( uuid ) ; + Gtk::TreeModelColumnRecord::add( is_power ) ; + } + Gtk::TreeModelColumn name; + Gtk::TreeModelColumn uuid; + Gtk::TreeModelColumn is_power; + } ; + ListColumns list_columns; + Block *block; + bool power_only = false; + bool bus_mode = false; + bool bus_member_mode = false; + Bus *bus = nullptr; + + + Gtk::TreeView *view; + Glib::RefPtr store; + + //type_signal_selected s_signal_selected; + type_signal_selected s_signal_activated; + void row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column); + }; + + +} diff --git a/widgets/sheet_box.cpp b/widgets/sheet_box.cpp new file mode 100644 index 000000000..50e759352 --- /dev/null +++ b/widgets/sheet_box.cpp @@ -0,0 +1,136 @@ +#include "sheet_box.hpp" +#include +#include + +namespace horizon { + SheetBox::SheetBox(CoreSchematic *c): + Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL, 0), + core(c) + { + auto *la = Gtk::manage(new Gtk::Label()); + la->set_markup("Sheets"); + la->show(); + pack_start(*la, false, false, 0); + + store = Gtk::ListStore::create(list_columns); + store->set_sort_column(list_columns.index, Gtk::SORT_ASCENDING); + view = Gtk::manage(new Gtk::TreeView(store)); + + view->append_column("", list_columns.index); + + { + auto cr = Gtk::manage(new Gtk::CellRendererText()); + auto tvc = Gtk::manage(new Gtk::TreeViewColumn("Sheet", *cr)); + tvc->add_attribute(*cr, "text", list_columns.name); + cr->property_editable().set_value(true); + cr->signal_edited().connect(sigc::mem_fun(this, &SheetBox::name_edited)); + view->append_column(*tvc); + } + { + auto cr = Gtk::manage(new Gtk::CellRendererPixbuf()); + cr->property_icon_name().set_value("dialog-warning-symbolic"); + auto tvc = Gtk::manage(new Gtk::TreeViewColumn("", *cr)); + tvc->add_attribute(*cr, "visible", list_columns.has_warnings); + view->append_column(*tvc); + } + + view->show(); + auto sc = Gtk::manage(new Gtk::ScrolledWindow()); + sc->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC); + sc->set_shadow_type(Gtk::SHADOW_IN); + sc->set_min_content_height(150); + sc->add(*view); + sc->show_all(); + + view->get_selection()->signal_changed().connect(sigc::mem_fun(this, &SheetBox::selection_changed)); + pack_start(*sc, true, true, 0); + + + auto tb = Gtk::manage(new Gtk::Toolbar()); + tb->get_style_context()->add_class("inline-toolbar"); + tb->set_icon_size(Gtk::ICON_SIZE_MENU); + tb->set_toolbar_style(Gtk::TOOLBAR_ICONS); + { + auto tbo = Gtk::manage(new Gtk::ToolButton()); + tbo->set_icon_name("list-add-symbolic"); + tbo->signal_clicked().connect([this]{s_signal_add_sheet.emit();}); + tb->insert(*tbo, -1); + } + { + auto tbo = Gtk::manage(new Gtk::ToolButton()); + tbo->set_icon_name("list-remove-symbolic"); + tbo->signal_clicked().connect(sigc::mem_fun(this, &SheetBox::remove_clicked)); + tb->insert(*tbo, -1); + remove_button = tbo; + } + update(); + selection_changed(); + tb->show_all(); + pack_start(*tb, false, false, 0); + + } + + void SheetBox::remove_clicked() { + auto it = view->get_selection()->get_selected(); + if(it) { + Gtk::TreeModel::Row row = *it; + signal_remove_sheet().emit(&core->get_schematic()->sheets.at(row[list_columns.uuid])); + } + else { + signal_remove_sheet().emit(nullptr); + } + } + + void SheetBox::selection_changed() { + auto it = view->get_selection()->get_selected(); + if(it) { + Gtk::TreeModel::Row row = *it; + if(core->get_schematic()->sheets.count(row[list_columns.uuid])) { + auto sh = &core->get_schematic()->sheets.at(row[list_columns.uuid]); + signal_select_sheet().emit(sh); + auto s = sh->symbols.size()==0; + remove_button->set_sensitive(s); + } + } + } + + void SheetBox::name_edited(const Glib::ustring& path, const Glib::ustring& new_text) { + auto it = store->get_iter(path); + if(it) { + Gtk::TreeModel::Row row = *it; + core->get_schematic()->sheets.at(row[list_columns.uuid]).name = new_text; + core->commit(); + core->rebuild(); + } + } + + + void SheetBox::update() { + auto uuid_from_core = core->get_sheet()->uuid; + auto s = core->get_schematic()->sheets.size()>1; + remove_button->set_sensitive(s); + + Gtk::TreeModel::Row row; + store->freeze_notify(); + store->clear(); + for(const auto &it: core->get_schematic()->sheets) { + row = *(store->append()); + row[list_columns.name] = it.second.name; + row[list_columns.uuid] = it.first; + row[list_columns.has_warnings] = it.second.warnings.size()>0; + row[list_columns.index] = it.second.index; + } + store->thaw_notify(); + + + for(const auto &it: store->children()) { + row = *it; + if(row[list_columns.uuid] == uuid_from_core) { + view->get_selection()->select(it); + break; + } + } + } + + +} diff --git a/widgets/sheet_box.hpp b/widgets/sheet_box.hpp new file mode 100644 index 000000000..736e740df --- /dev/null +++ b/widgets/sheet_box.hpp @@ -0,0 +1,50 @@ +#pragma once +#include +#include "core/core_schematic.hpp" +#include "sheet.hpp" + +namespace horizon { + class SheetBox: public Gtk::Box { + public: + SheetBox(CoreSchematic *c); + + void update(); + typedef sigc::signal type_signal_select_sheet; + type_signal_select_sheet signal_select_sheet() {return s_signal_select_sheet;} + typedef sigc::signal type_signal_add_sheet; + type_signal_add_sheet signal_add_sheet() {return s_signal_add_sheet;} + typedef sigc::signal type_signal_remove_sheet; + type_signal_remove_sheet signal_remove_sheet() {return s_signal_remove_sheet;} + + private: + class ListColumns : public Gtk::TreeModelColumnRecord { + public: + ListColumns() { + Gtk::TreeModelColumnRecord::add(name) ; + Gtk::TreeModelColumnRecord::add(uuid) ; + Gtk::TreeModelColumnRecord::add(index) ; + Gtk::TreeModelColumnRecord::add(has_warnings) ; + } + Gtk::TreeModelColumn name; + Gtk::TreeModelColumn uuid; + Gtk::TreeModelColumn index; + Gtk::TreeModelColumn has_warnings; + } ; + ListColumns list_columns; + + CoreSchematic *core; + Gtk::ToolButton *remove_button; + + Gtk::TreeView *view; + Glib::RefPtr store; + + type_signal_select_sheet s_signal_select_sheet; + type_signal_add_sheet s_signal_add_sheet; + type_signal_remove_sheet s_signal_remove_sheet; + void selection_changed(void); + void remove_clicked(void); + void name_edited(const Glib::ustring& path, const Glib::ustring& new_text); + }; + + +} diff --git a/widgets/spin_button_dim.cpp b/widgets/spin_button_dim.cpp new file mode 100644 index 000000000..169443957 --- /dev/null +++ b/widgets/spin_button_dim.cpp @@ -0,0 +1,33 @@ +#include "spin_button_dim.hpp" +#include + +namespace horizon { + SpinButtonDim::SpinButtonDim() : Gtk::SpinButton() { + set_increments(.1e6, 10); + set_width_chars(10); + } + + bool SpinButtonDim::on_output() { + auto adj = get_adjustment(); + double value = adj->get_value(); + + std::stringstream stream; + stream << std::fixed << std::setprecision(3) << value/1e6 << " mm"; + + set_text(stream.str()); + return true; + } + + int SpinButtonDim::on_input(double *v) { + auto txt = get_text(); + int64_t va = 0; + try { + va = std::stod(txt)*1e6; + *v = va; + } + catch (const std::exception& e) { + return false; + } + return true; + } +} diff --git a/widgets/spin_button_dim.hpp b/widgets/spin_button_dim.hpp new file mode 100644 index 000000000..bc12432c7 --- /dev/null +++ b/widgets/spin_button_dim.hpp @@ -0,0 +1,13 @@ +#pragma once +#include + +namespace horizon { + class SpinButtonDim: public Gtk::SpinButton { + public: + SpinButtonDim(); + + protected: + virtual int on_input(double* new_value); + virtual bool on_output(); + }; +} diff --git a/widgets/warnings_box.cpp b/widgets/warnings_box.cpp new file mode 100644 index 000000000..313efd9af --- /dev/null +++ b/widgets/warnings_box.cpp @@ -0,0 +1,56 @@ +#include "warnings_box.hpp" +#include +#include + +namespace horizon { + WarningsBox::WarningsBox(): + Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL, 4) + { + auto *la = Gtk::manage(new Gtk::Label()); + la->set_markup("Warnings"); + la->show(); + pack_start(*la, false, false, 0); + + store = Gtk::ListStore::create(list_columns); + view = Gtk::manage(new Gtk::TreeView(store)); + view->append_column("Text", list_columns.text); + view->show(); + auto sc = Gtk::manage(new Gtk::ScrolledWindow()); + sc->set_shadow_type(Gtk::SHADOW_IN); + sc->set_min_content_height(300); + sc->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC); + sc->add(*view); + sc->show_all(); + + view->signal_row_activated().connect(sigc::mem_fun(this, &WarningsBox::row_activated)); + pack_start(*sc, true, true, 0); + set_visible(false); + } + + void WarningsBox::row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column) { + auto it = store->get_iter(path); + if(it) { + Gtk::TreeModel::Row row = *it; + s_signal_selected.emit(row[list_columns.position]); + + + + } + } + + void WarningsBox::update(const std::vector &warnings) { + + Gtk::TreeModel::Row row; + store->freeze_notify(); + store->clear(); + set_visible(warnings.size()>0); + for(const auto &it: warnings) { + row = *(store->append()); + row[list_columns.text] = it.text; + row[list_columns.position] = it.position; + } + store->thaw_notify(); + } + + +} diff --git a/widgets/warnings_box.hpp b/widgets/warnings_box.hpp new file mode 100644 index 000000000..153efcaf7 --- /dev/null +++ b/widgets/warnings_box.hpp @@ -0,0 +1,35 @@ +#pragma once +#include +#include "core/core.hpp" +#include "sheet.hpp" + +namespace horizon { + class WarningsBox: public Gtk::Box { + public: + WarningsBox(); + + void update(const std::vector &warnings); + typedef sigc::signal type_signal_selected; + type_signal_selected signal_selected() {return s_signal_selected;} + + private: + class ListColumns : public Gtk::TreeModelColumnRecord { + public: + ListColumns() { + Gtk::TreeModelColumnRecord::add( text ) ; + Gtk::TreeModelColumnRecord::add( position ) ; + } + Gtk::TreeModelColumn text; + Gtk::TreeModelColumn position; + } ; + ListColumns list_columns; + + Gtk::TreeView *view; + Glib::RefPtr store; + + type_signal_selected s_signal_selected; + void row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column); + }; + + +} diff --git a/window.ui b/window.ui new file mode 100644 index 000000000..1c98a5e3f --- /dev/null +++ b/window.ui @@ -0,0 +1,196 @@ + + + + + + False + + + True + False + vertical + + + True + False + 8 + 8 + 8 + 8 + 16 + + + + + + + + + + + + False + True + 0 + + + + + True + False + 4 + + + True + False + vertical + + + True + False + + + True + False + 8 + 8 + 8 + 8 + vertical + + + + + + + + + + + + False + True + 0 + + + + + True + False + vertical + + + + + + True + True + 1 + + + + + True + True + 0 + + + + + True + False + 2 + 2 + 8 + True + + + True + False + Active tool: None + 0 + + + False + True + 0 + + + + + True + False + label + 0 + + + True + True + 1 + + + + + True + False + > + 0 + + + False + True + 2 + + + + + False + True + 1 + + + + + True + True + 0 + + + + + True + True + never + in + + + True + False + 4 + 8 + + + + + + + + False + True + 1 + + + + + True + True + 1 + + + + + + + + +