[{"author":"Mattia Basaglia","description":"Creates a frame from the area enclosing the selection.","license":"GPLv3+","name":"Concentric Frame","requires":"0.9.4","script":"concentric_frame.js","type":"script","version":"1","ui":"dialog.ui","icon":"frame-icon.svg","category":"Other","plugin_last_modified":"2016-10-24T10:16:34+02:00","plugin_shortname":"plugin_concentric_frame","plugin_files":["\nvar graph = document.graph;\nvar nodes = graph.selected_nodes;\n\nif ( nodes.length < 3 )\n{\n\twindow.dialog.information(\"Please select at leat 3 nodes\",\"Concentric Frame\");\n}\nelse if ( Dialog.exec() )\n{\n\n\n var current = nodes[0];\n var previous = current;\n var loop_nodes = [];\n\n for ( var i = 0; i < nodes.length; i++ )\n {\n loop_nodes.push(current);\n\n var found = false;\n for ( var j = 0; j < current.edges.length; j++ )\n {\n var other = current.edges[j].other(current);\n if ( previous != other )\n {\n previous = current;\n current = other;\n found = true;\n break;\n }\n }\n if ( !found )\n break;\n }\n\n\n\tdocument.begin_macro(\"Concentic Frames\");\n\n\t\/\/\/ get outer frame and midpoint\n\tvar outer_frame = [];\n\tvar midpoint = new Point(0,0);\n\tfor ( var i = 0; i < loop_nodes.length; i++ )\n\t{\n\n\t\tvar next_node = graph.add_node(loop_nodes[i].pos);\n\t\touter_frame.push(next_node);\n\n\t\tif ( i > 0 )\n\t\t\tgraph.connect(outer_frame[i-1],next_node);\n\n\t\tmidpoint.x += loop_nodes[i].x \/ loop_nodes.length;\n\t\tmidpoint.y += loop_nodes[i].y \/ loop_nodes.length;\n\n\t}\n\tgraph.connect(outer_frame[outer_frame.length-1],outer_frame[0]);\n\t\n\t\/\/ get average distance from midpoint\n\tvar base_length = 0;\n\tfor ( var i = 0; i < outer_frame.length; i++ )\n\t{\n\t\tbase_length += distance(midpoint,outer_frame[i].pos) \/ outer_frame.length;\n\t}\n\n\tif ( Dialog.check_delete_original.checked )\n\t\tfor ( var j = 0; j < nodes.length; j++ )\n\t\t\tgraph.remove_node(nodes[j]);\n\t\n\n\n\tvar n_steps = Dialog.spin_iterations.value;\n\tvar width = Dialog.spin_distance.value;\n\tvar outest_frame = outer_frame\n\tvar step = width\/n_steps;\n\tfor ( var j = 1; j <= n_steps; j++ )\n\t{\n\t\t\/\/ Build the new polygon scaling from the middle point\n\t\tvar inner_frame = [];\n\t\tvar factor = (base_length-j*step) \/ base_length;\n\t\tfor ( var i = 0; i < outer_frame.length; i++ )\n\t\t{\n\t\t\tvar offsetter = new Line ( midpoint, outest_frame[i].pos );\n\t\t\toffsetter.length *= factor;\n\t\t\tvar next_node = graph.add_node(offsetter.p2);\n\t\t\tinner_frame.push(next_node);\n\t\t\tif ( i > 0 )\n\t\t\t\tgraph.connect(inner_frame[i-1],next_node);\n\t\t\tif ( Dialog.check_connected.checked )\n\t\t\t\tgraph.connect(outer_frame[i],next_node);\n\t\t}\n\t\tgraph.connect(inner_frame[inner_frame.length-1],inner_frame[0]);\n\t\n\t\touter_frame = inner_frame;\n\t}\n\n\tdocument.end_macro();\n\n}","\n\n Dialog<\/class>\n \n \n \n 0<\/x>\n 0<\/y>\n 184<\/width>\n 155<\/height>\n <\/rect>\n <\/property>\n \n Concentric Frames<\/string>\n <\/property>\n \n \n \n \n px<\/string>\n <\/property>\n \n 8192.000000000000000<\/double>\n <\/property>\n \n 80.000000000000000<\/double>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n \n 0<\/horstretch>\n 0<\/verstretch>\n <\/sizepolicy>\n <\/property>\n \n Width<\/string>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n \n 0<\/horstretch>\n 0<\/verstretch>\n <\/sizepolicy>\n <\/property>\n \n Iterations<\/string>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Connected<\/string>\n <\/property>\n \n true<\/bool>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Qt::Horizontal<\/enum>\n <\/property>\n \n QDialogButtonBox::Cancel|QDialogButtonBox::Ok<\/set>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n 1<\/number>\n <\/property>\n \n 64<\/number>\n <\/property>\n \n 4<\/number>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Delete original selection<\/string>\n <\/property>\n \n true<\/bool>\n <\/property>\n <\/widget>\n <\/item>\n <\/layout>\n <\/widget>\n \n \n \n buttonBox<\/sender>\n accepted()<\/signal>\n Dialog<\/receiver>\n accept()<\/slot>\n \n \n 248<\/x>\n 254<\/y>\n <\/hint>\n \n 157<\/x>\n 274<\/y>\n <\/hint>\n <\/hints>\n <\/connection>\n \n buttonBox<\/sender>\n rejected()<\/signal>\n Dialog<\/receiver>\n reject()<\/slot>\n \n \n 316<\/x>\n 260<\/y>\n <\/hint>\n \n 286<\/x>\n 274<\/y>\n <\/hint>\n <\/hints>\n <\/connection>\n <\/connections>\n<\/ui>\n","\n\n\n\n \n \n \n \n \n image\/svg+xml<\/dc:format>\n \n <\/cc:Work>\n <\/rdf:RDF>\n <\/metadata>\n \n \n \n \n \n \n \n <\/g>\n \n \n \n \n \n <\/g>\n \n \n \n \n \n \n <\/g>\n <\/g>\n \n \n \n \n \n <\/g>\n \n \n \n \n \n <\/g>\n \n \n \n \n \n <\/g>\n \n \n \n \n \n <\/g>\n \n \n \n \n \n <\/g>\n \n \n <\/g>\n<\/svg>\n","{\n\t\"author\" : \"Mattia Basaglia\",\n\t\"description\" : \"Creates a frame from the area enclosing the selection.\",\n\t\"license\" : \"GPLv3+\",\n\t\"name\" : \"Concentric Frame\",\n\t\"requires\" : \"0.9.4\",\n\t\"script\" : \"concentric_frame.js\",\n\t\"type\" : \"script\",\n\t\"version\" : \"1\",\n \"ui\" : \"dialog.ui\",\n \"icon\" : \"frame-icon.svg\"\n}\n"]},{"name":"Example Cusp","version":"1","description":"An implementation of the built-in rounded cusp as a plugin, disabled by default.\n\nThis plugin serves as an example on how to create cusp plugins.","author":"Mattia Basaglia","license":"GPLv3+","type":"cusp","icon":"cusp-round","script":"example_cusp.js","requires":"0.9.2","auto_enable":false,"category":"Cusp","plugin_last_modified":"2016-10-24T10:16:34+02:00","plugin_shortname":"plugin_example_cusp","plugin_files":["\/*\nThe scripting language is QtScript aka ECMAScript aka JavaScript \n\nFunctions:\n * line() or new line() to create a line object (see QLine for reference)\n * point() or new point() to create a point (has properties 'x' and 'y')\n * opposite(p) returns point(-p.x,-p.y)\n\nVariables:\n * angle [real] - The angle between input and output edge\n * cusp_angle [real] - Style setting, if angle > cusp_angle, draw a cusp\n * handle_length [real] - Style setting, \"curve\" in the knot style dialog\n * start_handle [line] - starting point for lines, start_handle.p1 will be connected to the edge crossing\n * finish_handle [line] - ending point for lines, finish_handle.p1 will be connected to the edge crossing\n * cusp_point [point]- pre-computed cusp point location\n * node_point [point]- position of the node between the two edges\n * path [path] - object used to build the path\n*\/\n\n\/\/ When angle is large enough, draw the cusp\nif ( angle > cusp_angle ) \n{\n \/\/ Create a \"handle\" that on cusp_point with size handle_length to determine the control points\n var handle = new Line(start_handle.p1,finish_handle.p1);\n handle.translate(cusp_point);\n handle.translate(opposite(start_handle.p1));\n handle.length = handle_length;\n var h2 = handle.p2;\n handle.length = -handle_length;\n var h1 = handle.p2;\n \/\/ Use the control points to render the cusp\n path.add_cubic ( start_handle.p1, start_handle.p2, h1, cusp_point );\n path.add_cubic ( finish_handle.p1, finish_handle.p2, h2, cusp_point );\n}\nelse\n{\n \/\/ Don't draw a cusp, just a curve from start_handle to finish_handle\n \n if ( distance(start_handle.p1,finish_handle.p1) < start_handle.length + finish_handle.length )\n {\n \/\/ The two edges are very close together, avoid artifacts with a simpler curve\n var midpoint = Point( (start_handle.p2.x+finish_handle.p2.x)\/2,\n (start_handle.p2.y+finish_handle.p2.y)\/2 );\n path.add_quad(start_handle.p1,midpoint,finish_handle.p1);\n }\n else\n {\n \/\/ There's enough room to draw a cubic curve\n path.add_cubic(start_handle.p1,start_handle.p2,finish_handle.p2,finish_handle.p1);\n }\n} \n","{\n \"name\" : \"Example cusp\",\n \"version\" : \"1\",\n \"description\" : \"An implementation of the built-in rounded cusp as a plugin, disabled by default.\\n\\nThis plugin serves as an example on how to create cusp plugins.\",\n \"author\" : \"Mattia Basaglia\",\n \"license\" : \"GPLv3+\",\n \"type\" : \"cusp\",\n \"icon\" : \"cusp-round\",\n \"script\" : \"example_cusp.js\",\n \"requires\" : \"0.9.2\",\n \"auto_enable\" : false,\n \"category\" : \"Example\"\n}\n \n"]},{"author":"Mattia Basaglia","description":"An implementation of the built-in normal edge type as a plugin, disabled by default.\n\nThis plugin serves as an example on how to create crossing plugins.","license":"GPLv3+","name":"Example Edge","requires":"0.9.6","script":"example_edge.js","type":"crossing","version":"1","traverse":"var result = traverse(edge,handle,style);","handle":"var result = handle_line(edge,handle,style);","auto_enable":false,"category":"Example","plugin_last_modified":"2016-10-24T10:16:34+02:00","plugin_shortname":"plugin_example_edge","plugin_files":["function traverse(edge,handle,style)\n{\n var next = 0;\n if ( handle == TOP_RIGHT )\n next = BOTTOM_LEFT;\n else if ( handle == BOTTOM_RIGHT )\n next = TOP_LEFT;\n else if ( handle == BOTTOM_LEFT )\n next = TOP_RIGHT;\n else if ( handle == TOP_LEFT )\n next = BOTTOM_RIGHT;\n\n if ( handle == TOP_LEFT || next == TOP_LEFT )\n path.add_line(handle_line(edge,handle,style).p1,\n handle_line(edge,next,style).p1\n );\n return next;\n}\n\nfunction deg2rad(deg)\n{\n return deg \/ 180 * Math.PI;\n}\n\nfunction handle_line(edge,handle,style)\n{\n\n var handle_angle = 0;\n if ( handle == TOP_RIGHT )\n handle_angle = Math.PI\/4.0;\n else if ( handle == TOP_LEFT )\n handle_angle = Math.PI*3.0\/4.0;\n else if ( handle == BOTTOM_LEFT )\n handle_angle = Math.PI*5.0\/4.0;\n else if ( handle == BOTTOM_RIGHT )\n handle_angle = Math.PI*7.0\/4.0;\n\n handle_angle += deg2rad(edge.line.angle);\n var p1 = edge.line.pointAt(style.slide);\n p1.x = p1.x+style.gap\/2*Math.cos(handle_angle);\n p1.y = p1.y-style.gap\/2*Math.sin(handle_angle);\n\n var p2 = new Point(\n p1.x+style.curve*Math.cos(handle_angle),\n p1.y-style.curve*Math.sin(handle_angle)\n );\n\n return new Line(p1,p2);\n}","{\n \"author\" : \"Mattia Basaglia\",\n \"description\" : \"An implementation of the built-in normal edge type as a plugin, disabled by default.\\n\\nThis plugin serves as an example on how to create crossing plugins.\",\n \"license\" : \"GPLv3+\",\n \"name\" : \"Example Edge\",\n \"requires\" : \"0.9.6\",\n \"script\" : \"example_edge.js\",\n \"type\" : \"crossing\",\n \"version\" : \"1\",\n \"traverse\" : \"var result = traverse(edge,handle,style);\",\n \"handle\" : \"var result = handle_line(edge,handle,style);\",\n \"auto_enable\" : false,\n \"category\" : \"Example\"\n}"]},{"author":"Mattia Basaglia","category":"External","description":"Shows a dialog that allows the configuration of external programs.","icon":"configure","license":"GPLv3+","name":"Set Up External Programs","requires":"0.9.6","script":"config.js","type":"script","version":"1","plugin_settings":"external_programs","ui":"config.ui","settings":{"programs":[["GIMP","PNG","gimp"],["Inkscape","SVG","inkscape"]]},"plugin_last_modified":"2016-10-24T10:16:34+02:00","plugin_shortname":"plugin_external_config","plugin_files":["function execute_external(program_id)\n{\n var found = 0;\n for ( var i = 0; i < plugin.settings.programs.length; i++ )\n {\n item = plugin.settings.programs[i];\n if ( item[0] == program_id )\n {\n found = 1;\n var format_upper = item[1].toUpperCase();\n var format_lower = item[1].toLowerCase();\n var command = item[2];\n var data;\n if ( format_lower == \"svg\" )\n data = document.render.svg();\n else\n data = document.render.raster(0,0,format_upper);\n var tmpfile = system.unique_temp_file(\"knotter\",format_lower);\n system.write_file(tmpfile,data);\n if ( ! system.exec(command,[tmpfile]) )\n {\n window.dialog.critical(\"Cannot launch \"+program_id+\": \"+command);\n }\n }\n }\n if ( ! found )\n {\n throw \"Program \"+program_id+\" not found\";\n }\n}\n","var items = plugin.settings.programs;\nvar script_table = gui.table_widget(Dialog.tableWidget);\n\nfunction add()\n{\n var item = [\"New Program\",\"PNG\",\"\"];\n items.push(item);\n script_table.append_row(item);\n}\nfunction remove()\n{ \n var current_row = script_table.current_column();\n\tif ( current_row >= 0 )\n {\n\t\tDialog.tableWidget.removeRow(current_row);\n items.splice(current_row,1);\n }\n}\nfunction item_changed( r, c, text )\n{\n items[r][c] = text;\n}\n\n\/\/ populate teble\nDialog.tableWidget.clearContents();\nDialog.tableWidget.rowCount = 0;\nfor(var i = 0; i < items.length; i++)\n{\n script_table.append_row(items[i]);\n}\n\nscript_table.value_changed.connect(item_changed);\nDialog.button_add.clicked.connect(add);\nDialog.button_remove.clicked.connect(remove);\nDialog.exec();\n\/\/ Prevent multiple slot calls on re-execution:\nDialog.button_add.clicked.disconnect(add);\nDialog.button_remove.clicked.disconnect(remove);\n\n\/\/print(JSON.stringify(items,null,4));\n","\n\n Dialog<\/class>\n \n \n \n 0<\/x>\n 0<\/y>\n 358<\/width>\n 274<\/height>\n <\/rect>\n <\/property>\n \n Configure external commands<\/string>\n <\/property>\n \n \n \n \n QAbstractItemView::SingleSelection<\/enum>\n <\/property>\n \n QAbstractItemView::SelectRows<\/enum>\n <\/property>\n \n true<\/bool>\n <\/attribute>\n \n false<\/bool>\n <\/attribute>\n \n \n Name<\/string>\n <\/property>\n <\/column>\n \n \n Export<\/string>\n <\/property>\n <\/column>\n \n \n Executable<\/string>\n <\/property>\n <\/column>\n <\/widget>\n <\/item>\n \n \n \n \n \n Remove<\/string>\n <\/property>\n \n \n \n <\/iconset>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Add<\/string>\n <\/property>\n \n \n \n <\/iconset>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Qt::Horizontal<\/enum>\n <\/property>\n \n QDialogButtonBox::Ok<\/set>\n <\/property>\n <\/widget>\n <\/item>\n <\/layout>\n <\/item>\n <\/layout>\n <\/widget>\n \n \n \n buttonBox<\/sender>\n accepted()<\/signal>\n Dialog<\/receiver>\n accept()<\/slot>\n \n \n 248<\/x>\n 254<\/y>\n <\/hint>\n \n 157<\/x>\n 273<\/y>\n <\/hint>\n <\/hints>\n <\/connection>\n \n buttonBox<\/sender>\n rejected()<\/signal>\n Dialog<\/receiver>\n reject()<\/slot>\n \n \n 316<\/x>\n 260<\/y>\n <\/hint>\n \n 286<\/x>\n 273<\/y>\n <\/hint>\n <\/hints>\n <\/connection>\n <\/connections>\n<\/ui>\n","run_script(plugin.plugin_dir+\"\/common.js\");\n\nexecute_external(\"GIMP\");\n","run_script(plugin.plugin_dir+\"\/common.js\");\nexecute_external(\"Inkscape\");\n","var items =[];\nfor ( var i = 0; i < plugin.settings.programs.length; i++ )\n items.push(plugin.settings.programs[i][0]);\n\nvar item = window.dialog.get_item(\"Select program\",\"Launch External Program\",items);\nif ( item != \"\" )\n{\n run_script(plugin.plugin_dir+\"\/common.js\");\n execute_external(item);\n}\n","{\n\t\"author\" : \"Mattia Basaglia\",\n\t\"category\" : \"External\",\n\t\"description\" : \"Shows a dialog that allows the configuration of external programs.\",\n\t\"icon\" : \"configure\",\n\t\"license\" : \"GPLv3+\",\n\t\"name\" : \"Set up external programs\",\n\t\"requires\" : \"0.9.6\",\n \"script\" : \"config.js\",\n\t\"type\" : \"script\",\n\t\"version\" : \"1\",\n\t\"plugin_settings\" : \"external_programs\",\n \"ui\" : \"config.ui\",\n \"settings\": { \"programs\":[\n [ \"GIMP\", \"PNG\", \"gimp\" ],\n [\"Inkscape\", \"SVG\", \"inkscape\" ]\n ]}\n}\n","{\n \"author\" : \"Mattia Basaglia\",\n \"category\" : \"External\",\n \"description\" : \"Shows a dialog that allows to choose an external program to launch.\",\n \"icon\" : \"system-run\",\n \"license\" : \"GPLv3+\",\n \"name\" : \"Launch external programs\",\n \"requires\" : \"0.9.6\",\n \"script\" : \"other.js\",\n \"type\" : \"script\",\n \"version\" : \"1\",\n \"plugin_settings\" : \"external_programs\"\n}\n","{\n\t\"author\" : \"Mattia Basaglia\",\n\t\"category\" : \"External\",\n\t\"description\" : \"Opens a temporary PNG render with GIMP\",\n\t\"license\" : \"GPLv3+\",\n\t\"name\" : \"GIMP\",\n\t\"requires\" : \"0.9.6\",\n\t\"script\" : \"gimp.js\",\n\t\"type\" : \"script\",\n\t\"version\" : \"1\",\n\t\"plugin_settings\" : \"external_programs\"\n}","{\n\t\"author\" : \"Mattia Basaglia\",\n\t\"category\" : \"External\",\n\t\"description\" : \"Opens a temporary SVG render with Inkscape\",\n\t\"license\" : \"GPLv3+\",\n\t\"name\" : \"Inkscape\",\n\t\"requires\" : \"0.9.6\",\n\t\"script\" : \"inkscape.js\",\n\t\"type\" : \"script\",\n\t\"version\" : \"1\",\n\t\"plugin_settings\" : \"external_programs\"\n}"]},{"author":"Mattia Basaglia","category":"External","description":"Shows a dialog that allows to choose an external program to launch.","icon":"system-run","license":"GPLv3+","name":"Launch External Programs","requires":"0.9.6","script":"other.js","type":"script","version":"1","plugin_settings":"external_programs","plugin_last_modified":"2016-10-24T10:16:34+02:00","plugin_shortname":"plugin_external_other","plugin_files":["function execute_external(program_id)\n{\n var found = 0;\n for ( var i = 0; i < plugin.settings.programs.length; i++ )\n {\n item = plugin.settings.programs[i];\n if ( item[0] == program_id )\n {\n found = 1;\n var format_upper = item[1].toUpperCase();\n var format_lower = item[1].toLowerCase();\n var command = item[2];\n var data;\n if ( format_lower == \"svg\" )\n data = document.render.svg();\n else\n data = document.render.raster(0,0,format_upper);\n var tmpfile = system.unique_temp_file(\"knotter\",format_lower);\n system.write_file(tmpfile,data);\n if ( ! system.exec(command,[tmpfile]) )\n {\n window.dialog.critical(\"Cannot launch \"+program_id+\": \"+command);\n }\n }\n }\n if ( ! found )\n {\n throw \"Program \"+program_id+\" not found\";\n }\n}\n","var items = plugin.settings.programs;\nvar script_table = gui.table_widget(Dialog.tableWidget);\n\nfunction add()\n{\n var item = [\"New Program\",\"PNG\",\"\"];\n items.push(item);\n script_table.append_row(item);\n}\nfunction remove()\n{ \n var current_row = script_table.current_column();\n\tif ( current_row >= 0 )\n {\n\t\tDialog.tableWidget.removeRow(current_row);\n items.splice(current_row,1);\n }\n}\nfunction item_changed( r, c, text )\n{\n items[r][c] = text;\n}\n\n\/\/ populate teble\nDialog.tableWidget.clearContents();\nDialog.tableWidget.rowCount = 0;\nfor(var i = 0; i < items.length; i++)\n{\n script_table.append_row(items[i]);\n}\n\nscript_table.value_changed.connect(item_changed);\nDialog.button_add.clicked.connect(add);\nDialog.button_remove.clicked.connect(remove);\nDialog.exec();\n\/\/ Prevent multiple slot calls on re-execution:\nDialog.button_add.clicked.disconnect(add);\nDialog.button_remove.clicked.disconnect(remove);\n\n\/\/print(JSON.stringify(items,null,4));\n","\n\n Dialog<\/class>\n \n \n \n 0<\/x>\n 0<\/y>\n 358<\/width>\n 274<\/height>\n <\/rect>\n <\/property>\n \n Configure external commands<\/string>\n <\/property>\n \n \n \n \n QAbstractItemView::SingleSelection<\/enum>\n <\/property>\n \n QAbstractItemView::SelectRows<\/enum>\n <\/property>\n \n true<\/bool>\n <\/attribute>\n \n false<\/bool>\n <\/attribute>\n \n \n Name<\/string>\n <\/property>\n <\/column>\n \n \n Export<\/string>\n <\/property>\n <\/column>\n \n \n Executable<\/string>\n <\/property>\n <\/column>\n <\/widget>\n <\/item>\n \n \n \n \n \n Remove<\/string>\n <\/property>\n \n \n \n <\/iconset>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Add<\/string>\n <\/property>\n \n \n \n <\/iconset>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Qt::Horizontal<\/enum>\n <\/property>\n \n QDialogButtonBox::Ok<\/set>\n <\/property>\n <\/widget>\n <\/item>\n <\/layout>\n <\/item>\n <\/layout>\n <\/widget>\n \n \n \n buttonBox<\/sender>\n accepted()<\/signal>\n Dialog<\/receiver>\n accept()<\/slot>\n \n \n 248<\/x>\n 254<\/y>\n <\/hint>\n \n 157<\/x>\n 273<\/y>\n <\/hint>\n <\/hints>\n <\/connection>\n \n buttonBox<\/sender>\n rejected()<\/signal>\n Dialog<\/receiver>\n reject()<\/slot>\n \n \n 316<\/x>\n 260<\/y>\n <\/hint>\n \n 286<\/x>\n 273<\/y>\n <\/hint>\n <\/hints>\n <\/connection>\n <\/connections>\n<\/ui>\n","run_script(plugin.plugin_dir+\"\/common.js\");\n\nexecute_external(\"GIMP\");\n","run_script(plugin.plugin_dir+\"\/common.js\");\nexecute_external(\"Inkscape\");\n","var items =[];\nfor ( var i = 0; i < plugin.settings.programs.length; i++ )\n items.push(plugin.settings.programs[i][0]);\n\nvar item = window.dialog.get_item(\"Select program\",\"Launch External Program\",items);\nif ( item != \"\" )\n{\n run_script(plugin.plugin_dir+\"\/common.js\");\n execute_external(item);\n}\n","{\n\t\"author\" : \"Mattia Basaglia\",\n\t\"category\" : \"External\",\n\t\"description\" : \"Shows a dialog that allows the configuration of external programs.\",\n\t\"icon\" : \"configure\",\n\t\"license\" : \"GPLv3+\",\n\t\"name\" : \"Set up external programs\",\n\t\"requires\" : \"0.9.6\",\n \"script\" : \"config.js\",\n\t\"type\" : \"script\",\n\t\"version\" : \"1\",\n\t\"plugin_settings\" : \"external_programs\",\n \"ui\" : \"config.ui\",\n \"settings\": { \"programs\":[\n [ \"GIMP\", \"PNG\", \"gimp\" ],\n [\"Inkscape\", \"SVG\", \"inkscape\" ]\n ]}\n}\n","{\n \"author\" : \"Mattia Basaglia\",\n \"category\" : \"External\",\n \"description\" : \"Shows a dialog that allows to choose an external program to launch.\",\n \"icon\" : \"system-run\",\n \"license\" : \"GPLv3+\",\n \"name\" : \"Launch external programs\",\n \"requires\" : \"0.9.6\",\n \"script\" : \"other.js\",\n \"type\" : \"script\",\n \"version\" : \"1\",\n \"plugin_settings\" : \"external_programs\"\n}\n","{\n\t\"author\" : \"Mattia Basaglia\",\n\t\"category\" : \"External\",\n\t\"description\" : \"Opens a temporary PNG render with GIMP\",\n\t\"license\" : \"GPLv3+\",\n\t\"name\" : \"GIMP\",\n\t\"requires\" : \"0.9.6\",\n\t\"script\" : \"gimp.js\",\n\t\"type\" : \"script\",\n\t\"version\" : \"1\",\n\t\"plugin_settings\" : \"external_programs\"\n}","{\n\t\"author\" : \"Mattia Basaglia\",\n\t\"category\" : \"External\",\n\t\"description\" : \"Opens a temporary SVG render with Inkscape\",\n\t\"license\" : \"GPLv3+\",\n\t\"name\" : \"Inkscape\",\n\t\"requires\" : \"0.9.6\",\n\t\"script\" : \"inkscape.js\",\n\t\"type\" : \"script\",\n\t\"version\" : \"1\",\n\t\"plugin_settings\" : \"external_programs\"\n}"]},{"author":"Mattia Basaglia","category":"External","description":"Opens a temporary PNG render with GIMP","license":"GPLv3+","name":"GIMP","requires":"0.9.6","script":"gimp.js","type":"script","version":"1","plugin_settings":"external_programs","plugin_last_modified":"2016-10-24T10:16:34+02:00","plugin_shortname":"plugin_gimp","plugin_files":["function execute_external(program_id)\n{\n var found = 0;\n for ( var i = 0; i < plugin.settings.programs.length; i++ )\n {\n item = plugin.settings.programs[i];\n if ( item[0] == program_id )\n {\n found = 1;\n var format_upper = item[1].toUpperCase();\n var format_lower = item[1].toLowerCase();\n var command = item[2];\n var data;\n if ( format_lower == \"svg\" )\n data = document.render.svg();\n else\n data = document.render.raster(0,0,format_upper);\n var tmpfile = system.unique_temp_file(\"knotter\",format_lower);\n system.write_file(tmpfile,data);\n if ( ! system.exec(command,[tmpfile]) )\n {\n window.dialog.critical(\"Cannot launch \"+program_id+\": \"+command);\n }\n }\n }\n if ( ! found )\n {\n throw \"Program \"+program_id+\" not found\";\n }\n}\n","var items = plugin.settings.programs;\nvar script_table = gui.table_widget(Dialog.tableWidget);\n\nfunction add()\n{\n var item = [\"New Program\",\"PNG\",\"\"];\n items.push(item);\n script_table.append_row(item);\n}\nfunction remove()\n{ \n var current_row = script_table.current_column();\n\tif ( current_row >= 0 )\n {\n\t\tDialog.tableWidget.removeRow(current_row);\n items.splice(current_row,1);\n }\n}\nfunction item_changed( r, c, text )\n{\n items[r][c] = text;\n}\n\n\/\/ populate teble\nDialog.tableWidget.clearContents();\nDialog.tableWidget.rowCount = 0;\nfor(var i = 0; i < items.length; i++)\n{\n script_table.append_row(items[i]);\n}\n\nscript_table.value_changed.connect(item_changed);\nDialog.button_add.clicked.connect(add);\nDialog.button_remove.clicked.connect(remove);\nDialog.exec();\n\/\/ Prevent multiple slot calls on re-execution:\nDialog.button_add.clicked.disconnect(add);\nDialog.button_remove.clicked.disconnect(remove);\n\n\/\/print(JSON.stringify(items,null,4));\n","\n\n Dialog<\/class>\n \n \n \n 0<\/x>\n 0<\/y>\n 358<\/width>\n 274<\/height>\n <\/rect>\n <\/property>\n \n Configure external commands<\/string>\n <\/property>\n \n \n \n \n QAbstractItemView::SingleSelection<\/enum>\n <\/property>\n \n QAbstractItemView::SelectRows<\/enum>\n <\/property>\n \n true<\/bool>\n <\/attribute>\n \n false<\/bool>\n <\/attribute>\n \n \n Name<\/string>\n <\/property>\n <\/column>\n \n \n Export<\/string>\n <\/property>\n <\/column>\n \n \n Executable<\/string>\n <\/property>\n <\/column>\n <\/widget>\n <\/item>\n \n \n \n \n \n Remove<\/string>\n <\/property>\n \n \n \n <\/iconset>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Add<\/string>\n <\/property>\n \n \n \n <\/iconset>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Qt::Horizontal<\/enum>\n <\/property>\n \n QDialogButtonBox::Ok<\/set>\n <\/property>\n <\/widget>\n <\/item>\n <\/layout>\n <\/item>\n <\/layout>\n <\/widget>\n \n \n \n buttonBox<\/sender>\n accepted()<\/signal>\n Dialog<\/receiver>\n accept()<\/slot>\n \n \n 248<\/x>\n 254<\/y>\n <\/hint>\n \n 157<\/x>\n 273<\/y>\n <\/hint>\n <\/hints>\n <\/connection>\n \n buttonBox<\/sender>\n rejected()<\/signal>\n Dialog<\/receiver>\n reject()<\/slot>\n \n \n 316<\/x>\n 260<\/y>\n <\/hint>\n \n 286<\/x>\n 273<\/y>\n <\/hint>\n <\/hints>\n <\/connection>\n <\/connections>\n<\/ui>\n","run_script(plugin.plugin_dir+\"\/common.js\");\n\nexecute_external(\"GIMP\");\n","run_script(plugin.plugin_dir+\"\/common.js\");\nexecute_external(\"Inkscape\");\n","var items =[];\nfor ( var i = 0; i < plugin.settings.programs.length; i++ )\n items.push(plugin.settings.programs[i][0]);\n\nvar item = window.dialog.get_item(\"Select program\",\"Launch External Program\",items);\nif ( item != \"\" )\n{\n run_script(plugin.plugin_dir+\"\/common.js\");\n execute_external(item);\n}\n","{\n\t\"author\" : \"Mattia Basaglia\",\n\t\"category\" : \"External\",\n\t\"description\" : \"Shows a dialog that allows the configuration of external programs.\",\n\t\"icon\" : \"configure\",\n\t\"license\" : \"GPLv3+\",\n\t\"name\" : \"Set up external programs\",\n\t\"requires\" : \"0.9.6\",\n \"script\" : \"config.js\",\n\t\"type\" : \"script\",\n\t\"version\" : \"1\",\n\t\"plugin_settings\" : \"external_programs\",\n \"ui\" : \"config.ui\",\n \"settings\": { \"programs\":[\n [ \"GIMP\", \"PNG\", \"gimp\" ],\n [\"Inkscape\", \"SVG\", \"inkscape\" ]\n ]}\n}\n","{\n \"author\" : \"Mattia Basaglia\",\n \"category\" : \"External\",\n \"description\" : \"Shows a dialog that allows to choose an external program to launch.\",\n \"icon\" : \"system-run\",\n \"license\" : \"GPLv3+\",\n \"name\" : \"Launch external programs\",\n \"requires\" : \"0.9.6\",\n \"script\" : \"other.js\",\n \"type\" : \"script\",\n \"version\" : \"1\",\n \"plugin_settings\" : \"external_programs\"\n}\n","{\n\t\"author\" : \"Mattia Basaglia\",\n\t\"category\" : \"External\",\n\t\"description\" : \"Opens a temporary PNG render with GIMP\",\n\t\"license\" : \"GPLv3+\",\n\t\"name\" : \"GIMP\",\n\t\"requires\" : \"0.9.6\",\n\t\"script\" : \"gimp.js\",\n\t\"type\" : \"script\",\n\t\"version\" : \"1\",\n\t\"plugin_settings\" : \"external_programs\"\n}","{\n\t\"author\" : \"Mattia Basaglia\",\n\t\"category\" : \"External\",\n\t\"description\" : \"Opens a temporary SVG render with Inkscape\",\n\t\"license\" : \"GPLv3+\",\n\t\"name\" : \"Inkscape\",\n\t\"requires\" : \"0.9.6\",\n\t\"script\" : \"inkscape.js\",\n\t\"type\" : \"script\",\n\t\"version\" : \"1\",\n\t\"plugin_settings\" : \"external_programs\"\n}"]},{"author":"Mattia Basaglia","category":"External","description":"Opens a temporary SVG render with Inkscape","license":"GPLv3+","name":"Inkscape","requires":"0.9.6","script":"inkscape.js","type":"script","version":"1","plugin_settings":"external_programs","plugin_last_modified":"2016-10-24T10:16:34+02:00","plugin_shortname":"plugin_inkscape","plugin_files":["function execute_external(program_id)\n{\n var found = 0;\n for ( var i = 0; i < plugin.settings.programs.length; i++ )\n {\n item = plugin.settings.programs[i];\n if ( item[0] == program_id )\n {\n found = 1;\n var format_upper = item[1].toUpperCase();\n var format_lower = item[1].toLowerCase();\n var command = item[2];\n var data;\n if ( format_lower == \"svg\" )\n data = document.render.svg();\n else\n data = document.render.raster(0,0,format_upper);\n var tmpfile = system.unique_temp_file(\"knotter\",format_lower);\n system.write_file(tmpfile,data);\n if ( ! system.exec(command,[tmpfile]) )\n {\n window.dialog.critical(\"Cannot launch \"+program_id+\": \"+command);\n }\n }\n }\n if ( ! found )\n {\n throw \"Program \"+program_id+\" not found\";\n }\n}\n","var items = plugin.settings.programs;\nvar script_table = gui.table_widget(Dialog.tableWidget);\n\nfunction add()\n{\n var item = [\"New Program\",\"PNG\",\"\"];\n items.push(item);\n script_table.append_row(item);\n}\nfunction remove()\n{ \n var current_row = script_table.current_column();\n\tif ( current_row >= 0 )\n {\n\t\tDialog.tableWidget.removeRow(current_row);\n items.splice(current_row,1);\n }\n}\nfunction item_changed( r, c, text )\n{\n items[r][c] = text;\n}\n\n\/\/ populate teble\nDialog.tableWidget.clearContents();\nDialog.tableWidget.rowCount = 0;\nfor(var i = 0; i < items.length; i++)\n{\n script_table.append_row(items[i]);\n}\n\nscript_table.value_changed.connect(item_changed);\nDialog.button_add.clicked.connect(add);\nDialog.button_remove.clicked.connect(remove);\nDialog.exec();\n\/\/ Prevent multiple slot calls on re-execution:\nDialog.button_add.clicked.disconnect(add);\nDialog.button_remove.clicked.disconnect(remove);\n\n\/\/print(JSON.stringify(items,null,4));\n","\n\n Dialog<\/class>\n \n \n \n 0<\/x>\n 0<\/y>\n 358<\/width>\n 274<\/height>\n <\/rect>\n <\/property>\n \n Configure external commands<\/string>\n <\/property>\n \n \n \n \n QAbstractItemView::SingleSelection<\/enum>\n <\/property>\n \n QAbstractItemView::SelectRows<\/enum>\n <\/property>\n \n true<\/bool>\n <\/attribute>\n \n false<\/bool>\n <\/attribute>\n \n \n Name<\/string>\n <\/property>\n <\/column>\n \n \n Export<\/string>\n <\/property>\n <\/column>\n \n \n Executable<\/string>\n <\/property>\n <\/column>\n <\/widget>\n <\/item>\n \n \n \n \n \n Remove<\/string>\n <\/property>\n \n \n \n <\/iconset>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Add<\/string>\n <\/property>\n \n \n \n <\/iconset>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Qt::Horizontal<\/enum>\n <\/property>\n \n QDialogButtonBox::Ok<\/set>\n <\/property>\n <\/widget>\n <\/item>\n <\/layout>\n <\/item>\n <\/layout>\n <\/widget>\n \n \n \n buttonBox<\/sender>\n accepted()<\/signal>\n Dialog<\/receiver>\n accept()<\/slot>\n \n \n 248<\/x>\n 254<\/y>\n <\/hint>\n \n 157<\/x>\n 273<\/y>\n <\/hint>\n <\/hints>\n <\/connection>\n \n buttonBox<\/sender>\n rejected()<\/signal>\n Dialog<\/receiver>\n reject()<\/slot>\n \n \n 316<\/x>\n 260<\/y>\n <\/hint>\n \n 286<\/x>\n 273<\/y>\n <\/hint>\n <\/hints>\n <\/connection>\n <\/connections>\n<\/ui>\n","run_script(plugin.plugin_dir+\"\/common.js\");\n\nexecute_external(\"GIMP\");\n","run_script(plugin.plugin_dir+\"\/common.js\");\nexecute_external(\"Inkscape\");\n","var items =[];\nfor ( var i = 0; i < plugin.settings.programs.length; i++ )\n items.push(plugin.settings.programs[i][0]);\n\nvar item = window.dialog.get_item(\"Select program\",\"Launch External Program\",items);\nif ( item != \"\" )\n{\n run_script(plugin.plugin_dir+\"\/common.js\");\n execute_external(item);\n}\n","{\n\t\"author\" : \"Mattia Basaglia\",\n\t\"category\" : \"External\",\n\t\"description\" : \"Shows a dialog that allows the configuration of external programs.\",\n\t\"icon\" : \"configure\",\n\t\"license\" : \"GPLv3+\",\n\t\"name\" : \"Set up external programs\",\n\t\"requires\" : \"0.9.6\",\n \"script\" : \"config.js\",\n\t\"type\" : \"script\",\n\t\"version\" : \"1\",\n\t\"plugin_settings\" : \"external_programs\",\n \"ui\" : \"config.ui\",\n \"settings\": { \"programs\":[\n [ \"GIMP\", \"PNG\", \"gimp\" ],\n [\"Inkscape\", \"SVG\", \"inkscape\" ]\n ]}\n}\n","{\n \"author\" : \"Mattia Basaglia\",\n \"category\" : \"External\",\n \"description\" : \"Shows a dialog that allows to choose an external program to launch.\",\n \"icon\" : \"system-run\",\n \"license\" : \"GPLv3+\",\n \"name\" : \"Launch external programs\",\n \"requires\" : \"0.9.6\",\n \"script\" : \"other.js\",\n \"type\" : \"script\",\n \"version\" : \"1\",\n \"plugin_settings\" : \"external_programs\"\n}\n","{\n\t\"author\" : \"Mattia Basaglia\",\n\t\"category\" : \"External\",\n\t\"description\" : \"Opens a temporary PNG render with GIMP\",\n\t\"license\" : \"GPLv3+\",\n\t\"name\" : \"GIMP\",\n\t\"requires\" : \"0.9.6\",\n\t\"script\" : \"gimp.js\",\n\t\"type\" : \"script\",\n\t\"version\" : \"1\",\n\t\"plugin_settings\" : \"external_programs\"\n}","{\n\t\"author\" : \"Mattia Basaglia\",\n\t\"category\" : \"External\",\n\t\"description\" : \"Opens a temporary SVG render with Inkscape\",\n\t\"license\" : \"GPLv3+\",\n\t\"name\" : \"Inkscape\",\n\t\"requires\" : \"0.9.6\",\n\t\"script\" : \"inkscape.js\",\n\t\"type\" : \"script\",\n\t\"version\" : \"1\",\n\t\"plugin_settings\" : \"external_programs\"\n}"]},{"name":"Fill Area","version":"2.0","description":"Detects an area or an outline of the active selection and fills it with a dense graph.","author":"Mattia Basaglia","license":"GPLv3+","script":"fill_area.js","ui":"dialog.ui","icon":"fill-color","requires":"0.9.4","type":"script","category":"Other","plugin_last_modified":"2016-10-24T10:16:34+02:00","plugin_shortname":"plugin_fill_area","plugin_files":["\n\n Dialog<\/class>\n \n \n \n 0<\/x>\n 0<\/y>\n 193<\/width>\n 253<\/height>\n <\/rect>\n <\/property>\n \n Fill Area<\/string>\n <\/property>\n \n \n \n \n Grid Size<\/string>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Polygon<\/string>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n \n 0<\/horstretch>\n 0<\/verstretch>\n <\/sizepolicy>\n <\/property>\n \n px<\/string>\n <\/property>\n \n 1.000000000000000<\/double>\n <\/property>\n \n 1024.000000000000000<\/double>\n <\/property>\n \n 32.000000000000000<\/double>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Qt::Horizontal<\/enum>\n <\/property>\n \n QDialogButtonBox::Cancel|QDialogButtonBox::Ok<\/set>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Delete original selection<\/string>\n <\/property>\n \n true<\/bool>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n 0<\/number>\n <\/property>\n \n \n Convex Hull<\/string>\n <\/property>\n <\/item>\n \n \n Single Edge Loop<\/string>\n <\/property>\n <\/item>\n <\/widget>\n <\/item>\n \n \n \n Outline<\/string>\n <\/property>\n \n true<\/bool>\n <\/property>\n \n false<\/bool>\n <\/property>\n \n \n \n \n Thikness<\/string>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n \n 0<\/horstretch>\n 0<\/verstretch>\n <\/sizepolicy>\n <\/property>\n \n px<\/string>\n <\/property>\n \n 1.000000000000000<\/double>\n <\/property>\n \n 1024.000000000000000<\/double>\n <\/property>\n \n 32.000000000000000<\/double>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Outset<\/string>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Inset<\/string>\n <\/property>\n \n true<\/bool>\n <\/property>\n <\/widget>\n <\/item>\n <\/layout>\n <\/widget>\n <\/item>\n <\/layout>\n <\/widget>\n \n \n \n buttonBox<\/sender>\n accepted()<\/signal>\n Dialog<\/receiver>\n accept()<\/slot>\n \n \n 183<\/x>\n 243<\/y>\n <\/hint>\n \n 157<\/x>\n 138<\/y>\n <\/hint>\n <\/hints>\n <\/connection>\n \n buttonBox<\/sender>\n rejected()<\/signal>\n Dialog<\/receiver>\n reject()<\/slot>\n \n \n 183<\/x>\n 243<\/y>\n <\/hint>\n \n 192<\/x>\n 138<\/y>\n <\/hint>\n <\/hints>\n <\/connection>\n <\/connections>\n<\/ui>\n","\/*\n\tFind the convex hull, references as kept as indices\n Gift wrap algorithm\n *\/\nfunction convex_hull(nodes, start_index )\n{\n\t \/\/ Start from the index with minimum x, pointing upwards \n var hull_node = start_index;\n var angle_in = 90;\n var polygon = new Polygon();\n \/\/ Keep looping 'till we get back to the starting point\n do\n {\n \/\/ Add current node to the hull\n polygon.add_vertex(nodes[hull_node].pos);\n \/\/ Pick the next point\n var next_hull_node = (hull_node+1)%nodes.length;\n var angle_delta = 360;\n var next_angle = angle_in;\n \/\/ Find the point with minimum angle difference\n for ( var i = 0; i < nodes.length; i++ )\n {\n if ( i != hull_node )\n {\n var angle_out = (new Line(nodes[hull_node].pos,nodes[i].pos)).angle;\n\n var delta = angle_in - angle_out;\n\n if ( delta < 0 )\n delta += 360;\n\n if ( delta < angle_delta )\n {\n angle_delta = delta;\n next_hull_node = i;\n next_angle = angle_out;\n }\n }\n }\n angle_in = next_angle;\n hull_node = next_hull_node;\n }\n while(hull_node != start_index );\n\n return polygon;\n}\n\nfunction edge_loop(nodes)\n{\n var current = nodes[0];\n var previous = current;\n var poly_indices = [];\n\n var polygon = new Polygon();\n\n \/\/ Build polygon\n for ( var i = 0; i <= nodes.length; i++ )\n {\n polygon.add_vertex(current.pos);\n\n var found = false;\n for ( var j = 0; j < current.edges.length; j++ )\n {\n var other = current.edges[j].other(current);\n if ( previous != other )\n {\n previous = current;\n current = other;\n found = true;\n break;\n }\n }\n if ( !found )\n break;\n }\n\n return polygon;\n}\n\nvar graph = document.graph;\nvar nodes = graph.selected_nodes;\n\nDialog.grid_size.value = document.grid.size\nif ( nodes.length < 3 )\n{\n window.dialog.information(\"Please select at leat 3 nodes\",\"Fill Area\");\n}\nelse if ( Dialog.exec() )\n{\n\n \/\/ build bounding box\n var min_pt = new Point(Number.MAX_VALUE,Number.MAX_VALUE);\n var max_pt = new Point(Number.MIN_VALUE,Number.MIN_VALUE);\n var min_x_index = 0;\n for ( var j = 0; j < nodes.length; j++ )\n {\n if ( nodes[j].x < min_pt.x )\n {\n min_x_index = j;\n min_pt.x = nodes[j].x;\n }\n if ( nodes[j].y < min_pt.y )\n min_pt.y = nodes[j].y;\n if ( nodes[j].y > max_pt.y )\n max_pt.y = nodes[j].y;\n if ( nodes[j].x > max_pt.x )\n max_pt.x = nodes[j].x;\n }\n\n\n\n \/\/ get the polygon\n var polygon = null;\n if ( Dialog.algorithm.currentIndex == 1 )\n polygon = edge_loop(nodes);\n else\n polygon = convex_hull(nodes,min_x_index);\n\n \/\/ If the user wants an outline, get the inset polygon\n\tvar inset_polygon = null;\n\tif ( Dialog.outline.checked )\n\t{\n\t\t\/\/ Find the polygon center\n\t\tvar midpoint = new Point(0,0);\n\t\tfor ( var i = 0; i < polygon.vertices.length; i++ )\n\t\t{\n\t\t\tmidpoint.x += polygon.vertices[i].x \/ polygon.vertices.length;\n\t\t\tmidpoint.y += polygon.vertices[i].y \/ polygon.vertices.length;\n\t\t}\n\n\t\t\/\/ Build the new polygon scaling from the middle point\n\t\tinset_polygon = new Polygon;\n\t\tfor ( var i = 0; i < polygon.vertices.length; i++ )\n\t\t{\n\t\t\tvar offsetter = new Line ( midpoint, polygon.vertices[i] );\n\t\t\tif ( Dialog.outline.outset.checked )\n\t\t\t{\n\t\t\t\toffsetter.length += Dialog.outline.thickness.value\n\t\t\t\t\/\/ if outset, the new polygon will be larger, thus increase the bounding box\t\t\t\n \t\tif ( offsetter.p2.x < min_pt.x )\n \t \tmin_pt.x = offsetter.p2.x;\n \t\tif ( offsetter.p2.y < min_pt.y )\n \t min_pt.y = offsetter.p2.y;\n \t\tif ( offsetter.p2.y > max_pt.y )\n \t max_pt.y = offsetter.p2.y;\n \t \tif ( offsetter.p2.x > max_pt.x )\n\t max_pt.x = offsetter.p2.x;\n\t\t\t}\n\t\t\telse\n\t\t\t\toffsetter.length -= Dialog.outline.thickness.value\n\t\t\tinset_polygon.add_vertex(offsetter.p2);\n\t\t}\n\t\t\/\/ If outset, swap external and internal polygons\n\t\tif ( Dialog.outline.outset.checked )\n\t\t{\n\t\t\tvar swap = polygon;\n\t\t\tpolygon = inset_polygon;\n\t\t\tinset_polygon = swap;;\n\t\t}\n\t}\n\n \/\/ Start filling the area\n if ( polygon.vertices.length > 2 )\n {\n document.begin_macro(\"Fill Area\");\n\n var size = Dialog.grid_size.value;\n var new_nodes = new Array();\n\n var max_steps = ((max_pt.y-min_pt.y)\/size)*((max_pt.x-min_pt.y)\/size);\n var progress_dialog = window.dialog.progress_dialog(\n \"Filling area\",max_steps,\"Interrupt\");\n progress_dialog.minimumDuration = 2000;\n\t\t\t\n\t\t\t\/\/ For each point in the grid\n for ( var y = min_pt.y, yi = 0; y <= max_pt.y; y += size, yi++ )\n {\n new_nodes.push([]);\n for ( var x = min_pt.x, xi = 0; x <= max_pt.x; x += size, xi++ )\n {\n progress_dialog.value = yi*((max_pt.x-min_pt.y)\/size)+xi;\n \n\t\t\t\t\t\t\/\/ if the point is inside the designated area, draw it\n\t\t\t\t\t\tvar outline = !inset_polygon || !inset_polygon.contains(x,y);\n if ( outline && ( \n\t\t\t\t\t\t\tpolygon.contains(x,y) || polygon.contains(x-1,y-1) ||\n polygon.contains(x-1,y+1) || polygon.contains(x+1,y-1) \n\t\t\t\t\t\t)\t)\n {\n var node = graph.add_node(x,y);\n new_nodes[yi].push(node);\n if ( yi > 0 )\n {\n var node_up = new_nodes[yi-1][xi];\n if ( node_up )\n graph.connect(node_up,node);\n }\n if ( x > min_pt.x )\n {\n var node_left = new_nodes[yi][xi-1];\n if ( node_left )\n graph.connect(node_left,node);\n }\n }\n else\n new_nodes[yi].push(null);\n\n }\n }\n\n\t\t\t\/\/ Optionally remove old nodes\n if ( Dialog.delete_original.checked )\n {\n for ( var i = 0; i < nodes.length; i++ )\n {\n graph.remove_node(nodes[i]);\n }\n }\n\n\t\t\tprogress_dialog.value = max_steps;\n\n document.end_macro();\n }\n}\n","{\n \"name\" : \"Fill Area\",\n \"version\" : \"2.0\",\n \"description\" : \"Detects an area or an outline of the active selection and fills it with a dense graph.\",\n \"author\" : \"Mattia Basaglia\",\n \"license\" : \"GPLv3+\",\n \"script\" : \"fill_area.js\",\n \"ui\" : \"dialog.ui\",\n \"icon\" : \"fill-color\",\n \"requires\" : \"0.9.4\"\n}\n\n"]},{"name":"Insert Lattice","version":"1.0","description":"Insert a square grid of connected nodes. Useful as a starting point for large knots.","author":"Mattia Basaglia","license":"GPLv3+","category":"Insert","icon":"grid-square","script":"lattice.js","ui":"dialog.ui","requires":"0.9.3","type":"script","plugin_last_modified":"2016-10-24T10:16:34+02:00","plugin_shortname":"plugin_lattice","plugin_files":["\n\n Dialog<\/class>\n \n \n \n 0<\/x>\n 0<\/y>\n 184<\/width>\n 131<\/height>\n <\/rect>\n <\/property>\n \n Insert Lattice<\/string>\n <\/property>\n \n \n \n \n Rows<\/string>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n 1<\/number>\n <\/property>\n \n 128<\/number>\n <\/property>\n \n 6<\/number>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Columns<\/string>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n 1<\/number>\n <\/property>\n \n 128<\/number>\n <\/property>\n \n 6<\/number>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Cell size<\/string>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n px<\/string>\n <\/property>\n \n 1024.000000000000000<\/double>\n <\/property>\n \n 32.000000000000000<\/double>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Qt::Horizontal<\/enum>\n <\/property>\n \n QDialogButtonBox::Cancel|QDialogButtonBox::Ok<\/set>\n <\/property>\n <\/widget>\n <\/item>\n <\/layout>\n <\/widget>\n \n \n \n buttonBox<\/sender>\n accepted()<\/signal>\n Dialog<\/receiver>\n accept()<\/slot>\n \n \n 248<\/x>\n 254<\/y>\n <\/hint>\n \n 157<\/x>\n 274<\/y>\n <\/hint>\n <\/hints>\n <\/connection>\n \n buttonBox<\/sender>\n rejected()<\/signal>\n Dialog<\/receiver>\n reject()<\/slot>\n \n \n 316<\/x>\n 260<\/y>\n <\/hint>\n \n 286<\/x>\n 274<\/y>\n <\/hint>\n <\/hints>\n <\/connection>\n <\/connections>\n<\/ui>\n","Dialog.cell_size.value = document.grid.size;\n\nif ( Dialog.exec() )\n{\n graph = new Graph();\n var x = 0;\n var y = 0;\n var size = Dialog.cell_size.value\n for ( var j = 0; j < Dialog.columns.value; j++ )\n {\n for ( var i = 0; i < Dialog.rows.value; i++ )\n {\n var node = graph.add_node(x,y);\n if ( i > 0 )\n {\n var left_node = graph.node_at(x-size,y);\n graph.connect(left_node,node);\n }\n if ( j > 0 )\n {\n var top_node = graph.node_at(x,y-size);\n graph.connect(top_node,node);\n }\n x += size;\n }\n x = 0;\n y += size;\n }\n document.insert(graph);\n}\n","{\n \"name\" : \"Insert lattice\",\n \"version\" : \"1.0\",\n \"description\" : \"Insert a square grid of connected nodes. Useful as a starting point for large knots.\",\n \"author\" : \"Mattia Basaglia\",\n \"license\" : \"GPLv3+\",\n \"category\" : \"Insert\",\n \"icon\" : \"grid-square\",\n \"script\" : \"lattice.js\",\n \"ui\" : \"dialog.ui\",\n \"requires\" : \"0.9.3\"\n}\n \n"]},{"author":"Mattia Basaglia","description":"Copies and mirrors the selection over the X axis, the Y axis or both.","license":"GPLv3+","name":"Mirror","script":"mirror.js","type":"script","ui":"dialog.ui","icon":"object-flip-horizontal","version":2,"requires":"0.9.5","category":"Other","plugin_last_modified":"2016-10-24T10:16:34+02:00","plugin_shortname":"plugin_mirror","plugin_files":["\n\n Dialog<\/class>\n \n \n \n 0<\/x>\n 0<\/y>\n 184<\/width>\n 101<\/height>\n <\/rect>\n <\/property>\n \n Mirror<\/string>\n <\/property>\n \n \n \n \n Mirror X<\/string>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Mirror Y<\/string>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Qt::Vertical<\/enum>\n <\/property>\n \n \n 20<\/width>\n 40<\/height>\n <\/size>\n <\/property>\n <\/spacer>\n <\/item>\n \n \n \n Qt::Horizontal<\/enum>\n <\/property>\n \n QDialogButtonBox::Cancel|QDialogButtonBox::Ok<\/set>\n <\/property>\n <\/widget>\n <\/item>\n <\/layout>\n <\/widget>\n \n \n \n buttonBox<\/sender>\n accepted()<\/signal>\n Dialog<\/receiver>\n accept()<\/slot>\n \n \n 248<\/x>\n 254<\/y>\n <\/hint>\n \n 157<\/x>\n 274<\/y>\n <\/hint>\n <\/hints>\n <\/connection>\n \n buttonBox<\/sender>\n rejected()<\/signal>\n Dialog<\/receiver>\n reject()<\/slot>\n \n \n 316<\/x>\n 260<\/y>\n <\/hint>\n \n 286<\/x>\n 274<\/y>\n <\/hint>\n <\/hints>\n <\/connection>\n <\/connections>\n<\/ui>\n","function mirror(x,y)\n{\n\tvar graph = document.graph;\n\tvar selected = graph.selected_nodes\n\tvar new_nodes = [];\n\n\tfor ( var i = 0; i < selected.length; i++ )\n\t{\n\t\tvar node_pos = selected[i].pos;\n\t\tif ( x*node_pos.x != node_pos.x || y*node_pos.y != node_pos.y )\n\t\t{\n\t\t\tvar node = graph.add_node(x*node_pos.x,y*node_pos.y);\n\t\t\tnode.style = selected[i].style;\n\t\t\tnew_nodes.push(node);\n\t\t\t\n\t\t}\n\t}\n\n\tfor ( var i = 0; i < selected.length; i++ )\n\t{\n\t\tvar node_pos = selected[i].pos;\n\t\tvar from = graph.node_at(x*node_pos.x,y*node_pos.y);\n\t\tif ( from )\n\t\t{\n\t\t\tfor ( var j = 0; j < selected[i].edges.length; j++ )\n\t\t\t{\n\t\t\t\t\tvar other_pos = selected[i].edges[j].other(selected[i]).pos;\n\t\t\t\t\tvar to = graph.node_at(x*other_pos.x,y*other_pos.y);\n\t\t\t\t\tif ( to )\n\t\t\t\t\t{\n\t\t\t\t\t\tvar edge = graph.connect(from,to);\n\t\t\t\t\t\tedge.style = selected[i].edges[j].style;\n\t\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor ( var i = 0; i < new_nodes.length; i++ )\n\t\tnew_nodes[i].selected = true;\n}\n\nif ( Dialog.exec() )\n{\n\tdocument.begin_macro(\"Mirror\");\n\tif ( Dialog.check_mirror_x.checked )\n\t\tmirror(-1,1);\n\tif ( Dialog.check_mirror_y.checked )\n\t\tmirror(1,-1);\n\tdocument.end_macro();\n}\n","{\n\t\"author\" : \"Mattia Basaglia\",\n\t\"description\" : \"Copies and mirrors the selection over the X axis, the Y axis or both.\",\n\t\"license\" : \"GPLv3+\",\n\t\"name\" : \"Mirror\",\n\t\"script\" : \"mirror.js\",\n\t\"type\" : \"script\",\n\t\"ui\" : \"dialog.ui\",\n\t\"icon\": \"object-flip-horizontal\",\n\t\"version\" : 2,\n\t\"requires\" : \"0.9.5\"\n}"]},{"name":"Insert Polygon","version":"1.0","description":"This plugin inserts a polygon. Can be useful to create knots with some rotational symmetry.","author":"Mattia Basaglia","license":"GPLv3+","category":"Insert","icon":"draw-polygon","script":"polygon.js","ui":"dialog_insert_polygon.ui","requires":"0.9.3","type":"script","plugin_last_modified":"2016-10-24T10:16:34+02:00","plugin_shortname":"plugin_polygon","plugin_files":["\n\n Dialog_Insert_Polygon<\/class>\n \n \n \n 0<\/x>\n 0<\/y>\n 184<\/width>\n 98<\/height>\n <\/rect>\n <\/property>\n \n Insert Polygon<\/string>\n <\/property>\n \n \n \n <\/iconset>\n <\/property>\n \n \n \n \n \n 0<\/horstretch>\n 0<\/verstretch>\n <\/sizepolicy>\n <\/property>\n \n Sides<\/string>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n 3<\/number>\n <\/property>\n \n 32<\/number>\n <\/property>\n \n 5<\/number>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Whether there should be a node connected to the vertices on the center of the polygon<\/string>\n <\/property>\n \n Node at Center<\/string>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Qt::Horizontal<\/enum>\n <\/property>\n \n QDialogButtonBox::Cancel|QDialogButtonBox::Ok<\/set>\n <\/property>\n <\/widget>\n <\/item>\n <\/layout>\n <\/widget>\n \n \n \n buttonBox<\/sender>\n accepted()<\/signal>\n Dialog_Insert_Polygon<\/receiver>\n accept()<\/slot>\n \n \n 248<\/x>\n 254<\/y>\n <\/hint>\n \n 157<\/x>\n 274<\/y>\n <\/hint>\n <\/hints>\n <\/connection>\n \n buttonBox<\/sender>\n rejected()<\/signal>\n Dialog_Insert_Polygon<\/receiver>\n reject()<\/slot>\n \n \n 316<\/x>\n 260<\/y>\n <\/hint>\n \n 286<\/x>\n 274<\/y>\n <\/hint>\n <\/hints>\n <\/connection>\n <\/connections>\n<\/ui>\n","{\n \"name\" : \"Insert Polygon\",\n \"version\" : \"1.0\",\n \"description\" : \"This plugin inserts a polygon. Can be useful to create knots with some rotational symmetry.\",\n \"author\" : \"Mattia Basaglia\",\n \"license\" : \"GPLv3+\",\n \"category\" : \"Insert\",\n \"icon\" : \"draw-polygon\",\n \"script\" : \"polygon.js\",\n \"ui\" : \"dialog_insert_polygon.ui\",\n \"requires\" : \"0.9.3\"\n}\n \n","if ( Dialog_Insert_Polygon.exec() )\n{\n var graph = new Graph();\n \n var last = null;\n var first = null;\n\n var radius = (document.grid.enabled ? document.grid.size : 32)*3;\n \n var sides = Dialog_Insert_Polygon.spin_sides.value;\n\n var middle = null;\n if ( Dialog_Insert_Polygon.check_middle_node.checked )\n middle = graph.add_node(0,0);\n\n for ( var i = 0; i < sides; i++ )\n {\n var angle = 2*Math.PI*i\/sides;\n\n var next = graph.add_node(radius*Math.cos(angle),-radius*Math.sin(angle));\n\n if ( !first )\n first = next;\n\n if ( middle )\n graph.connect(middle,next);\n\n if ( last )\n graph.connect(last,next);\n\n last = next;\n\n }\n graph.connect(last,first);\n\n \n document.insert(graph,\"Insert Polygon\");\n}\n"]},{"author":"Mattia Basaglia","description":"Randomize the position of the selected nodes by moving them within a certain distance from their starting position.","license":"GPLv3+","name":"Randomize","script":"randomize.js","type":"script","version":"1","requires":"0.9.4","category":"Other","plugin_last_modified":"2016-10-24T10:16:34+02:00","plugin_shortname":"plugin_randomize","plugin_files":["{\n\t\"author\" : \"Mattia Basaglia\",\n\t\"description\" : \"Randomize the position of the selected nodes by moving them within a certain distance from their starting position.\",\n\t\"license\" : \"GPLv3+\",\n\t\"name\" : \"Randomize\",\n\t\"script\" : \"randomize.js\",\n\t\"type\" : \"script\",\n\t\"version\" : \"1\",\n\t\"requires\" : \"0.9.4\"\n}","var max_distance=window.dialog.get_number(\"Maximum distance\",\"\",document.grid.size\/2,0);\n\nif ( !isNaN(max_distance) && document.graph.selected_nodes.length > 0 )\n{\n\tdocument.begin_macro(\"Randomize\");\n\n\tfor ( var i = 0; i < document.graph.selected_nodes.length; i++ )\n\t{\n\t\tvar node = document.graph.selected_nodes[i];\n\t\tvar line = new Line(node.pos,Point(node.pos.x+max_distance,node.pos.y));\n\t\tline.angle = 360 * Math.random();\n\t\tnode.pos = line.pointAt(Math.random());\n\t}\n\n\tdocument.end_macro();\n}"]},{"author":"Mattia Basaglia","description":"Merge overlapping nodes.","license":"GPLv3+","name":"Remove Duplicates","script":"remove_duplicates.js","type":"script","requires":"0.9.3","version":1,"category":"Other","plugin_last_modified":"2016-10-24T10:16:34+02:00","plugin_shortname":"plugin_remove_duplicates","plugin_files":["{\n\t\"author\" : \"Mattia Basaglia\",\n\t\"description\" : \"Merge overlapping nodes.\",\n\t\"license\" : \"GPLv3+\",\n\t\"name\" : \"Remove Duplicates\",\n\t\"script\" : \"remove_duplicates.js\",\n \"type\" : \"script\",\n \"requires\" : \"0.9.3\",\n \"version\" : 1\n}\n","\nvar radius = window.dialog.get_number(\"Radius\",\"\",0.1,0);\n\nif ( !isNaN(radius) )\n{\n\tdocument.begin_macro(\"Remove Duplicates\");\n\tfor ( var i = 0; i < document.graph.nodes.length; i++ )\n\t{\n\t\tvar base = document.graph.nodes[i];\n\t\tvar colliding = document.graph.nodes_at(base.pos,radius);\n\t\tfor ( var j = 0; j < colliding.length; j++ )\n\t\t{\n\t\t\tif ( colliding[j] != base )\n\t\t\t{\n\t\t\t\tfor ( var k = 0; k < colliding[j].edges.length; k++ )\n\t\t\t\t{\n\t\t\t\t\tif ( colliding[j].edges[k].other(colliding[j]) != base )\n\t\t\t\t\t\tdocument.graph.connect(base,colliding[j].edges[k].other(colliding[j]))\n\t\t\t\t}\n\t\t\t\tdocument.graph.remove_node(colliding[j]);\n\t\t\t}\n\t\t}\n\t}\n\tdocument.end_macro();\n}"]},{"description":"Insert an Archimedean spiral","name":"Spiral","requires":"0.9.4","script":"spiral.js","type":"script","version":"1","category":"Insert","ui":"dialog.ui","icon":"draw-spiral","plugin_last_modified":"2016-10-24T10:16:34+02:00","plugin_shortname":"plugin_spiral","plugin_files":["\n\n Dialog<\/class>\n \n \n \n 0<\/x>\n 0<\/y>\n 184<\/width>\n 161<\/height>\n <\/rect>\n <\/property>\n \n Spiral<\/string>\n <\/property>\n \n \n \n \n End Angle<\/string>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n \u00b0<\/string>\n <\/property>\n \n 8640.000000000000000<\/double>\n <\/property>\n \n 1800.000000000000000<\/double>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Qt::Horizontal<\/enum>\n <\/property>\n \n QDialogButtonBox::Cancel|QDialogButtonBox::Ok<\/set>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Start Angle<\/string>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n \u00b0<\/string>\n <\/property>\n \n 8640.000000000000000<\/double>\n <\/property>\n \n 360.000000000000000<\/double>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Turning Size<\/string>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n px<\/string>\n <\/property>\n \n 1024.000000000000000<\/double>\n <\/property>\n \n 32.000000000000000<\/double>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n \u00b0<\/string>\n <\/property>\n \n 1.000000000000000<\/double>\n <\/property>\n \n 360.000000000000000<\/double>\n <\/property>\n \n 15.000000000000000<\/double>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n \n 0<\/horstretch>\n 0<\/verstretch>\n <\/sizepolicy>\n <\/property>\n \n Angle Step<\/string>\n <\/property>\n <\/widget>\n <\/item>\n <\/layout>\n <\/widget>\n \n \n \n buttonBox<\/sender>\n accepted()<\/signal>\n Dialog<\/receiver>\n accept()<\/slot>\n \n \n 248<\/x>\n 254<\/y>\n <\/hint>\n \n 157<\/x>\n 274<\/y>\n <\/hint>\n <\/hints>\n <\/connection>\n \n buttonBox<\/sender>\n rejected()<\/signal>\n Dialog<\/receiver>\n reject()<\/slot>\n \n \n 316<\/x>\n 260<\/y>\n <\/hint>\n \n 286<\/x>\n 274<\/y>\n <\/hint>\n <\/hints>\n <\/connection>\n <\/connections>\n<\/ui>\n","{\n\t\"description\" : \"Insert an Archimedean spiral\",\n\t\"name\" : \"Spiral\",\n\t\"requires\" : \"0.9.4\",\n\t\"script\" : \"spiral.js\",\n\t\"type\" : \"script\",\n\t\"version\" : \"1\",\n\t\"category\" : \"Insert\",\n\t\"ui\" : \"dialog.ui\",\n\t\"icon\" : \"draw-spiral\"\n}","\nif ( Dialog.exec() )\n{\n\tvar prev_node = null;\n\tvar graph = new Graph;\n\tfor ( var a = Dialog.start_angle.value; a <= Dialog.end_angle.value; a+= Dialog.step_angle.value )\n\t{\n\t\tvar theta = Math.PI*a\/180;\n\t\tvar radius = theta*Dialog.turn_size.value\/(Math.PI*2);\n\t\tvar node = graph.add_node(radius*Math.cos(theta),-radius*Math.sin(theta));\n\t\tif ( prev_node )\n\t\t\tgraph.connect(prev_node,node);\n\t\tprev_node = node;\n\t}\n\n\tdocument.insert(graph,\"Insert Spiral\");\n}"]},{"author":"Mattia Basaglia","description":"Insert a star","license":"GPLv3+","name":"Star","requires":"0.9.4","script":"star.js","type":"script","version":"1","icon":"draw-star","category":"Insert","ui":"dialog.ui","plugin_last_modified":"2016-10-24T10:16:34+02:00","plugin_shortname":"plugin_star","plugin_files":["\n\n Dialog_Insert_Star<\/class>\n \n \n \n 0<\/x>\n 0<\/y>\n 184<\/width>\n 131<\/height>\n <\/rect>\n <\/property>\n \n Insert Star<\/string>\n <\/property>\n \n \n <\/property>\n \n \n \n \n \n 0<\/horstretch>\n 0<\/verstretch>\n <\/sizepolicy>\n <\/property>\n \n Sides<\/string>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n 3<\/number>\n <\/property>\n \n 32<\/number>\n <\/property>\n \n 5<\/number>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n \n 0<\/horstretch>\n 0<\/verstretch>\n <\/sizepolicy>\n <\/property>\n \n Inner Radius<\/string>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n px<\/string>\n <\/property>\n \n 1.000000000000000<\/double>\n <\/property>\n \n 1024.000000000000000<\/double>\n <\/property>\n \n 96.000000000000000<\/double>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n \n 0<\/horstretch>\n 0<\/verstretch>\n <\/sizepolicy>\n <\/property>\n \n Outer Radius<\/string>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n px<\/string>\n <\/property>\n \n 1.000000000000000<\/double>\n <\/property>\n \n 1024.000000000000000<\/double>\n <\/property>\n \n 192.000000000000000<\/double>\n <\/property>\n <\/widget>\n <\/item>\n \n \n \n Qt::Horizontal<\/enum>\n <\/property>\n \n QDialogButtonBox::Cancel|QDialogButtonBox::Ok<\/set>\n <\/property>\n <\/widget>\n <\/item>\n <\/layout>\n <\/widget>\n \n \n \n buttonBox<\/sender>\n accepted()<\/signal>\n Dialog_Insert_Star<\/receiver>\n accept()<\/slot>\n \n \n 248<\/x>\n 254<\/y>\n <\/hint>\n \n 157<\/x>\n 274<\/y>\n <\/hint>\n <\/hints>\n <\/connection>\n \n buttonBox<\/sender>\n rejected()<\/signal>\n Dialog_Insert_Star<\/receiver>\n reject()<\/slot>\n \n \n 316<\/x>\n 260<\/y>\n <\/hint>\n \n 286<\/x>\n 274<\/y>\n <\/hint>\n <\/hints>\n <\/connection>\n <\/connections>\n<\/ui>\n","{\n\t\"author\" : \"Mattia Basaglia\",\n\t\"description\" : \"Insert a star\",\n\t\"license\" : \"GPLv3+\",\n\t\"name\" : \"Star\",\n\t\"requires\" : \"0.9.4\",\n\t\"script\" : \"star.js\",\n\t\"type\" : \"script\",\n\t\"version\" : \"1\",\n\t\"icon\" : \"draw-star\",\n\t\"category\" : \"Insert\",\n\t\"ui\" : \"dialog.ui\"\n}","if ( Dialog_Insert_Star.exec() )\n{\n\tvar graph = new Graph();\n \n\tvar radius = Dialog_Insert_Star.spin_inner_radius.value;\n\tvar out_radius = Dialog_Insert_Star.spin_outer_radius.value;\n \n\tvar sides = Dialog_Insert_Star.spin_sides.value;\n\n\tvar edges = [];\n\n\tvar first = null;\n\tvar last = null;\n\n\tfor ( var i = 0; i < sides; i++ )\n\t{\n\t\tvar angle = 2*Math.PI*i\/sides;\n\n\t\tvar next = graph.add_node(radius*Math.cos(angle),-radius*Math.sin(angle));\n\n\t\tif ( !first )\n\t\t\tfirst = next;\n\n\t\tif ( last )\n\t\t\tedges.push(graph.connect(last,next));\n\n\t\tlast = next;\n\n\t}\n\n\tedges.push(graph.connect(last,first));\n\n\tfor ( var i = 0; i < edges.length; i++ )\n\t{\n\t\t\n\t\tvar angle = 2*Math.PI*i\/edges.length+Math.PI\/edges.length;\n\n\t\tvar node = graph.add_node(out_radius*Math.cos(angle),\n\t\t\t\t\t\t\t\t\t\t\t-out_radius*Math.sin(angle));\n\n\t\tgraph.connect(edges[i].vertex1,node);\n\t\tgraph.connect(node,edges[i].vertex2);\n\n\t}\t\t\n\n\n\tdocument.insert(graph,\"Insert Star\");\n}\n"]},{"name":"Star Cusp","version":"1.0","author":"Mattia Basaglia","description":"A cusp that looks like a star","license":"GPLv3+","type":"cusp","script":"star_cusp.js","requires":"0.9.2","category":"Cusp","plugin_last_modified":"2016-10-24T10:16:34+02:00","plugin_shortname":"plugin_star_cusp","plugin_files":["{\n \"name\" : \"Star cusp\",\n \"version\" : \"1.0\",\n \"author\" : \"Mattia Basaglia\",\n \"description\" : \"A cusp that looks like a star\",\n \"license\" : \"GPLv3+\",\n \"type\" : \"cusp\",\n \"script\" : \"star_cusp.js\",\n \"requires\" : \"0.9.2\"\n}\n \n","\nif ( angle > cusp_angle ) \n{\n cusp_line = Line(node_point,cusp_point);\n \/\/path.add_line(node_point,cusp_point);\n \n tip_line1 = Line(cusp_line)\n tip_line1.angle = cusp_line.angle-direction*angle\/4;\n in_line1 = Line(cusp_line);\n in_line1.angle = cusp_line.angle-direction*angle\/8;\n in_line1.length \/= 2;\n base_line1 = Line(in_line1);\n base_line1.angle = cusp_line.angle-direction*angle*3\/8;\n path.add_line(cusp_point,in_line1.p2);\n path.add_line(in_line1.p2,tip_line1.p2);\n path.add_line(tip_line1.p2,base_line1.p2);\n path.add_quad(base_line1.p2,start_handle.p2,start_handle.p1);\n \n tip_line2 = Line(cusp_line)\n tip_line2.angle = cusp_line.angle+direction*angle\/4;\n in_line2 = Line(cusp_line);\n in_line2.angle = cusp_line.angle+direction*angle\/8;\n in_line2.length \/= 2;\n base_line2 = Line(in_line1);\n base_line2.angle = cusp_line.angle+direction*angle*3\/8;\n path.add_line(cusp_point,in_line2.p2);\n path.add_line(in_line2.p2,tip_line2.p2);\n path.add_line(tip_line2.p2,base_line2.p2);\n path.add_quad(base_line2.p2,finish_handle.p2,finish_handle.p1);\n \n}\nelse if ( distance(start_handle.p1,finish_handle.p1) < start_handle.length + finish_handle.length )\n{\n var midpoint = Point( (start_handle.p2.x+finish_handle.p2.x)\/2,\n (start_handle.p2.y+finish_handle.p2.y)\/2 );\n path.add_quad(start_handle.p1,midpoint,finish_handle.p1);\n}\nelse\n{\n path.add_cubic(start_handle.p1,start_handle.p2,finish_handle.p2,finish_handle.p1);\n}\n"]},{"name":"Insert Text","description":"Inserts knotted text from a string.","category":"Insert","author":"Mattia Basaglia","license":"GPLv3+","script":"insert_text.js","icon":"insert-text","version":1,"requires":"0.9.4","type":"script","plugin_last_modified":"2016-10-24T10:16:34+02:00","plugin_shortname":"plugin_text","plugin_files":["\n\n