├── index.html ├── image ├── schematic │ ├── ldr.png │ ├── led.png │ ├── diode.png │ ├── dip10.png │ ├── dip12.png │ ├── dip14.png │ ├── dip16.png │ ├── dip4.png │ ├── dip6.png │ ├── dip8.png │ ├── dpdt1.png │ ├── dpdt2.png │ ├── ground.png │ ├── icon.png │ ├── lm386.png │ ├── spdt.png │ ├── spst.png │ ├── ammeter.png │ ├── battery.png │ ├── inductor.png │ ├── resistor.png │ ├── sinewave.png │ ├── capacitor.png │ ├── connector.png │ ├── capacitor_pol.png │ ├── potentiometer.png │ ├── dip8_opamp_dual.png │ ├── transistor_npn.png │ ├── transistor_pnp.png │ ├── dc_voltage_supply.png │ ├── dip8_opamp_single.png │ ├── inductor_coupling.png │ ├── transistor_n_jfet.png │ ├── transistor_p_jfet.png │ ├── opamp_triangle_3pin.png │ ├── transistor_n_mosfet.png │ ├── transistor_p_mosfet.png │ ├── transistor_n_regulator.png │ ├── transistor_p_regulator.png │ ├── transistor_npn_darlington.png │ └── transistor_pnp_darlington.png ├── common_emitter.png └── stripboard │ ├── TO-1.png │ ├── TO-5.png │ ├── TO-92.png │ ├── diode.png │ ├── icon.png │ ├── ldr.png │ ├── led.png │ ├── spdt.png │ ├── spst.png │ ├── TO-126.png │ ├── TO-220.png │ ├── pot_end.png │ ├── trimmer.png │ ├── inductor.png │ ├── pot_start.png │ ├── pot_wiper.png │ ├── resistor.png │ ├── capacitor_blob.png │ └── trimmer_inline.png ├── data ├── stripboard_package.tsv ├── schematic_specs_anchor.tsv ├── stripboard_specs_transistor.tsv ├── specs_tsv_to_js ├── schematic_specs_pin.tsv └── stripboard_specs_dip.tsv ├── README.md ├── js ├── stripboard │ ├── specs_package.js │ ├── global.js │ ├── render_outline.js │ ├── add.js │ ├── properties_dialog.js │ ├── component_resistor.js │ ├── component_led.js │ ├── component_ldr.js │ ├── component_diode.js │ ├── component_inductor.js │ ├── component_capacitor.js │ ├── render_default.js │ ├── component_link.js │ ├── component_connector.js │ ├── component_connector_battery.js │ ├── component_connector_switch_spst.js │ ├── component_connector_switch_spdt.js │ ├── netlist.js │ ├── components.js │ ├── component_connector_pot.js │ ├── component_trimmer.js │ ├── component_connector_switch_dpdt.js │ ├── specs_dip.js │ ├── component_transistor.js │ ├── component_dip.js │ └── grid.js ├── schematic │ ├── palette.js │ ├── copy_placement.js │ ├── global.js │ ├── autoplace.js │ ├── guide.js │ ├── netlist_import_pedalgen.js │ ├── label.js │ ├── autoplace2.js │ ├── netlist_import.js │ ├── anchor.js │ ├── components.js │ ├── nets.js │ ├── rotate_and_mirror.js │ ├── guides.js │ ├── share.js │ ├── net.js │ └── component.js ├── vec2 │ └── vec2.js └── kruskal │ └── kruskal.js ├── css ├── index.css └── sidebar.css ├── .github └── FUNDING.yml ├── schematic.html └── project └── naga_viper.schematic /index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /image/schematic/ldr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/ldr.png -------------------------------------------------------------------------------- /image/schematic/led.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/led.png -------------------------------------------------------------------------------- /image/common_emitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/common_emitter.png -------------------------------------------------------------------------------- /image/schematic/diode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/diode.png -------------------------------------------------------------------------------- /image/schematic/dip10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/dip10.png -------------------------------------------------------------------------------- /image/schematic/dip12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/dip12.png -------------------------------------------------------------------------------- /image/schematic/dip14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/dip14.png -------------------------------------------------------------------------------- /image/schematic/dip16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/dip16.png -------------------------------------------------------------------------------- /image/schematic/dip4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/dip4.png -------------------------------------------------------------------------------- /image/schematic/dip6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/dip6.png -------------------------------------------------------------------------------- /image/schematic/dip8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/dip8.png -------------------------------------------------------------------------------- /image/schematic/dpdt1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/dpdt1.png -------------------------------------------------------------------------------- /image/schematic/dpdt2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/dpdt2.png -------------------------------------------------------------------------------- /image/schematic/ground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/ground.png -------------------------------------------------------------------------------- /image/schematic/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/icon.png -------------------------------------------------------------------------------- /image/schematic/lm386.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/lm386.png -------------------------------------------------------------------------------- /image/schematic/spdt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/spdt.png -------------------------------------------------------------------------------- /image/schematic/spst.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/spst.png -------------------------------------------------------------------------------- /image/stripboard/TO-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/stripboard/TO-1.png -------------------------------------------------------------------------------- /image/stripboard/TO-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/stripboard/TO-5.png -------------------------------------------------------------------------------- /image/stripboard/TO-92.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/stripboard/TO-92.png -------------------------------------------------------------------------------- /image/stripboard/diode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/stripboard/diode.png -------------------------------------------------------------------------------- /image/stripboard/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/stripboard/icon.png -------------------------------------------------------------------------------- /image/stripboard/ldr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/stripboard/ldr.png -------------------------------------------------------------------------------- /image/stripboard/led.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/stripboard/led.png -------------------------------------------------------------------------------- /image/stripboard/spdt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/stripboard/spdt.png -------------------------------------------------------------------------------- /image/stripboard/spst.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/stripboard/spst.png -------------------------------------------------------------------------------- /image/schematic/ammeter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/ammeter.png -------------------------------------------------------------------------------- /image/schematic/battery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/battery.png -------------------------------------------------------------------------------- /image/schematic/inductor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/inductor.png -------------------------------------------------------------------------------- /image/schematic/resistor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/resistor.png -------------------------------------------------------------------------------- /image/schematic/sinewave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/sinewave.png -------------------------------------------------------------------------------- /image/stripboard/TO-126.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/stripboard/TO-126.png -------------------------------------------------------------------------------- /image/stripboard/TO-220.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/stripboard/TO-220.png -------------------------------------------------------------------------------- /image/stripboard/pot_end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/stripboard/pot_end.png -------------------------------------------------------------------------------- /image/stripboard/trimmer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/stripboard/trimmer.png -------------------------------------------------------------------------------- /image/schematic/capacitor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/capacitor.png -------------------------------------------------------------------------------- /image/schematic/connector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/connector.png -------------------------------------------------------------------------------- /image/stripboard/inductor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/stripboard/inductor.png -------------------------------------------------------------------------------- /image/stripboard/pot_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/stripboard/pot_start.png -------------------------------------------------------------------------------- /image/stripboard/pot_wiper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/stripboard/pot_wiper.png -------------------------------------------------------------------------------- /image/stripboard/resistor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/stripboard/resistor.png -------------------------------------------------------------------------------- /image/schematic/capacitor_pol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/capacitor_pol.png -------------------------------------------------------------------------------- /image/schematic/potentiometer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/potentiometer.png -------------------------------------------------------------------------------- /image/schematic/dip8_opamp_dual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/dip8_opamp_dual.png -------------------------------------------------------------------------------- /image/schematic/transistor_npn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/transistor_npn.png -------------------------------------------------------------------------------- /image/schematic/transistor_pnp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/transistor_pnp.png -------------------------------------------------------------------------------- /image/stripboard/capacitor_blob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/stripboard/capacitor_blob.png -------------------------------------------------------------------------------- /image/stripboard/trimmer_inline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/stripboard/trimmer_inline.png -------------------------------------------------------------------------------- /data/stripboard_package.tsv: -------------------------------------------------------------------------------- 1 | id package x y 2 | 1 TO-1 58 37 3 | 2 TO-5 58 37 4 | 3 TO-92 37 37 5 | 4 TO-126 37 37 6 | 5 TO-220 37 37 7 | -------------------------------------------------------------------------------- /image/schematic/dc_voltage_supply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/dc_voltage_supply.png -------------------------------------------------------------------------------- /image/schematic/dip8_opamp_single.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/dip8_opamp_single.png -------------------------------------------------------------------------------- /image/schematic/inductor_coupling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/inductor_coupling.png -------------------------------------------------------------------------------- /image/schematic/transistor_n_jfet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/transistor_n_jfet.png -------------------------------------------------------------------------------- /image/schematic/transistor_p_jfet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/transistor_p_jfet.png -------------------------------------------------------------------------------- /image/schematic/opamp_triangle_3pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/opamp_triangle_3pin.png -------------------------------------------------------------------------------- /image/schematic/transistor_n_mosfet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/transistor_n_mosfet.png -------------------------------------------------------------------------------- /image/schematic/transistor_p_mosfet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/transistor_p_mosfet.png -------------------------------------------------------------------------------- /image/schematic/transistor_n_regulator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/transistor_n_regulator.png -------------------------------------------------------------------------------- /image/schematic/transistor_p_regulator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/transistor_p_regulator.png -------------------------------------------------------------------------------- /image/schematic/transistor_npn_darlington.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/transistor_npn_darlington.png -------------------------------------------------------------------------------- /image/schematic/transistor_pnp_darlington.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvhx/stripboard2schematic/HEAD/image/schematic/transistor_pnp_darlington.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stripboard2schematic 2 | Tool to convert stripboard layouts back to schematics, either for checks or if schematic was lost 3 | 4 | This tool runs in browser, try it here: [stripboard2schematic online](https://dvhx.github.io/stripboard2schematic/stripboard.html) 5 | 6 | [](https://youtu.be/VD2KlX8a7o0) 7 | 8 | You can support development on [Patreon](https://www.patreon.com/DusanHalicky) 9 | -------------------------------------------------------------------------------- /js/stripboard/specs_package.js: -------------------------------------------------------------------------------- 1 | // Package pin offset specifications 2 | // This file was generated by data/specs_tsv_to_js 3 | 4 | SC.specsPackage = { 5 | "TO-1": { 6 | "x": 58, 7 | "y": 37 8 | }, 9 | "TO-5": { 10 | "x": 58, 11 | "y": 37 12 | }, 13 | "TO-92": { 14 | "x": 37, 15 | "y": 37 16 | }, 17 | "TO-126": { 18 | "x": 37, 19 | "y": 37 20 | }, 21 | "TO-220": { 22 | "x": 37, 23 | "y": 37 24 | } 25 | }; -------------------------------------------------------------------------------- /js/schematic/palette.js: -------------------------------------------------------------------------------- 1 | // Palette of 20 distinct colors 2 | "use strict"; 3 | // globals: document, window 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.palette = [ 8 | '#3cb44b', // green 9 | '#e6194B', // red 10 | '#ffe119', // yellow 11 | '#4363d8', // blue 12 | '#f58231', // orange 13 | '#911eb4', // purple 14 | '#42d4f4', // cyan 15 | '#f032e6', // magenta 16 | '#bfef45', // lime 17 | '#fabed4', // pink 18 | '#469990', // teal 19 | '#dcbeff', // lavender 20 | '#9A6324', // brown 21 | '#800000', // maroon 22 | '#aaffc3', // mint 23 | '#808000', // olive 24 | '#ffd8b1', // apricot 25 | '#000075', // navy 26 | '#555555', // grey1 27 | '#999999', // grey2 28 | '#bbbbbb' // grey3 29 | ]; 30 | 31 | -------------------------------------------------------------------------------- /js/schematic/copy_placement.js: -------------------------------------------------------------------------------- 1 | // Copy placement (component placement, guides) from other schematic file 2 | "use strict"; 3 | // globals: document, window 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.copyPlacement = function (aOther) { 8 | var i, j, cur, oth; 9 | for (i = 0; i < SC.components.item.length; i++) { 10 | cur = SC.components.item[i]; 11 | for (j = 0; j < aOther.components.item.length; j++) { 12 | oth = aOther.components.item[j]; 13 | if (cur.name === oth.name) { 14 | cur.x = oth.x; 15 | cur.y = oth.y; 16 | cur.mirror = oth.mirror; 17 | cur.rotate = oth.rotate; 18 | cur.updatePins(); 19 | } 20 | } 21 | } 22 | SC.guides.fromObject(aOther.guides); 23 | SC.render(); 24 | }; 25 | -------------------------------------------------------------------------------- /js/schematic/global.js: -------------------------------------------------------------------------------- 1 | // Global variables 2 | "use strict"; 3 | // globals: document, window, CA 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.show = { 8 | selection: true, 9 | net_numbers: false, 10 | net_crossings: true, 11 | guides: true, 12 | net_black: false 13 | }; 14 | 15 | SC.gridSize = 20; // grid size used in component images, e.g. resistor is 40x60 (2x3 grid size, pins are at [1,0] and [1,3]) 16 | 17 | SC.tool = 'select'; // Current tool 18 | 19 | SC.filename = 'noname.schematic'; // Current filename 20 | 21 | SC.autosave = true; // Save on page reload/quit 22 | 23 | SC.grayComponents = false; // If true components will have gray background (easier debugging) 24 | 25 | SC.clearStorageAfterImport = !false; // If true erase stored netlist after successful import 26 | 27 | SC.image = {}; // Image cache 28 | 29 | -------------------------------------------------------------------------------- /css/index.css: -------------------------------------------------------------------------------- 1 | /* main css */ 2 | 3 | body { 4 | margin: 0; 5 | display: flex; 6 | width: 100vw; 7 | height: 100vh; 8 | box-sizing: border-box; 9 | font-family: sans-serif; 10 | font-size: medium; 11 | } 12 | 13 | /* main with canvases */ 14 | 15 | main { 16 | flex: 1; 17 | box-sizing: border-box; 18 | display: flex; 19 | position: relative; 20 | } 21 | 22 | main canvas { 23 | position: absolute; 24 | box-sizing: border-box; 25 | top: 0; 26 | left: 0; 27 | width: 100%; 28 | height: 100%; 29 | } 30 | 31 | /* footer */ 32 | 33 | footer { 34 | position: fixed; 35 | z-index: 1; 36 | left: 0; 37 | bottom: 0; 38 | width: 100%; 39 | display: flex; 40 | background-color: silver; 41 | font-family: monospace; 42 | font-size: medium; 43 | } 44 | footer > div:nth-child(2n) { 45 | margin-right: 1ex; 46 | font-weight: bold; 47 | } 48 | 49 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: DusanHalicky # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /js/stripboard/global.js: -------------------------------------------------------------------------------- 1 | // Global variables 2 | "use strict"; 3 | // globals: document, window, CA 4 | 5 | var SC = window.SC || {}; 6 | 7 | // Which layers to show and opacity 8 | SC.show = { 9 | background: true, 10 | components: true, 11 | components_opacity: 0.5, 12 | copper: true, 13 | copper_opacity: 0.5, 14 | net_numbers: false, 15 | selection: true 16 | }; 17 | 18 | SC.tool = 'resistor'; // Current tool 19 | 20 | SC.filename = 'noname.stripboard'; // Current filename 21 | 22 | SC.factory = {}; // Constructors for different elements, used to restore components 23 | 24 | SC.autosave = true; // Save on page reload/quit 25 | 26 | // Image to show when no bg image is set 27 | SC.emptyImage = 'data:image/gif;base64,R0lGODlhAQABAHAAACH5BAUAAAAALAAAAAABAAEAAAICRAEAOw=='; 28 | 29 | // Location of schematic.html (should be on the same domain for import to work seamlesly) 30 | SC.netlist2SchematicUrl = 'schematic.html'; 31 | 32 | // Naming prefixes (R for resistor, C for capacitor) to which number will be added, e.g. R1, R2, C1 33 | SC.namingPrefix = {}; 34 | 35 | -------------------------------------------------------------------------------- /js/stripboard/render_outline.js: -------------------------------------------------------------------------------- 1 | // Render component outline 2 | "use strict"; 3 | // globals: document, window 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.renderOutline = function (aComponent, aContext, aColor) { 8 | // Render component outline 9 | // clear outline canvas 10 | var c = aContext, o = SC.show.components_opacity; 11 | c.globalCompositeOperation = 'source-over'; 12 | // draw component 4 times, offset 5px left/right/upd/down 13 | c.translate(-5, 0); 14 | aComponent.render(c); 15 | c.translate(10, 0); 16 | aComponent.render(c); 17 | c.translate(-5, -5); 18 | aComponent.render(c); 19 | c.translate(0, 10); 20 | aComponent.render(c); 21 | c.translate(0, -5); 22 | // use what is drawn to preserve the desired color 23 | c.globalCompositeOperation = 'source-in'; 24 | c.fillStyle = aColor; 25 | c.fillRect(0, 0, SC.layer.selected.width, SC.layer.selected.height); 26 | // erase the component itself so that we can see it, multiple because of transparency 27 | c.globalCompositeOperation = 'destination-out'; 28 | SC.show.components_opacity = 1; 29 | aComponent.render(c); 30 | SC.show.components_opacity = o; 31 | c.globalCompositeOperation = 'source-over'; 32 | }; 33 | -------------------------------------------------------------------------------- /js/stripboard/add.js: -------------------------------------------------------------------------------- 1 | // Adding new component 2 | "use strict"; 3 | // globals: document, window, CA 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.add = function (aComponent) { 8 | // Show properties dialog on just added component, remove it if user chooses cancel 9 | // Some components have validator function that return false if component is not allowed (usually wrong span) 10 | if (aComponent.validator && !aComponent.validator()) { 11 | SC.components.remove(aComponent); 12 | SC.grid.updateNets(); 13 | SC.render(); 14 | return; 15 | } 16 | // Show properties dialog just after adding 17 | var p = aComponent.propertiesDialog(function (aButton) { 18 | if (!aButton || aButton === 'Cancel') { 19 | // remove 20 | SC.components.remove(aComponent); 21 | SC.selected = null; 22 | } else { 23 | // add 24 | SC.components.item.push(aComponent); 25 | SC.selected = aComponent; 26 | } 27 | // re-render 28 | SC.layer.selected.clear(); 29 | SC.grid.updateNets(); 30 | SC.render(); 31 | }); 32 | // rename "Save" to "Add" 33 | if (p && p.dlg && p.dlg.buttons && p.dlg.buttons.Save) { 34 | p.dlg.buttons.Save.textContent = 'Add'; 35 | } 36 | return p; 37 | }; 38 | 39 | 40 | -------------------------------------------------------------------------------- /js/schematic/autoplace.js: -------------------------------------------------------------------------------- 1 | // Autoplacement of components after import 2 | "use strict"; 3 | // globals: document, window, CA 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.autoplaceByType = function () { 8 | // Place components in rows by their type, into columns by their number 9 | var i, o = {}, t, x = 0, y = 0, h, c, img; 10 | // sort by types 11 | for (i = 0; i < SC.components.item.length; i++) { 12 | o[SC.components.item[i].type] = o[SC.components.item[i].type] || []; 13 | o[SC.components.item[i].type].push(SC.components.item[i]); 14 | } 15 | 16 | function compareNameNumber(a, b) { 17 | // sort by number in name 18 | return parseFloat(a.name.match(/[0-9]+/)) - parseFloat(b.name.match(/[0-9]+/)); 19 | } 20 | 21 | // place components 22 | for (t in o) { 23 | if (o.hasOwnProperty(t)) { 24 | o[t].sort(compareNameNumber); 25 | x = 0; 26 | h = 0; 27 | for (i = 0; i < o[t].length; i++) { 28 | c = o[t][i]; 29 | c.x = x / SC.gridSize; 30 | c.y = y / SC.gridSize; 31 | c.rotate = 0; 32 | c.mirror = false; 33 | c.updatePins(); 34 | img = c.image(); 35 | h = Math.max(h, img.height); 36 | x += SC.gridSize * Math.ceil(img.width / SC.gridSize) + SC.gridSize; 37 | } 38 | y += SC.gridSize * Math.ceil(h / SC.gridSize) + SC.gridSize; 39 | } 40 | } 41 | }; 42 | 43 | -------------------------------------------------------------------------------- /data/schematic_specs_anchor.tsv: -------------------------------------------------------------------------------- 1 | id type name_x1 name_y1 name_x2 name_y2 value_x1 value_y1 value_x2 value_y2 2 | 1 capacitor 0 0 17 20 0 38 17 59 3 | 2 spst 24 18 39 36 24 18 39 36 4 | 3 spdt 33 9 59 22 33 9 59 22 5 | 4 dpdt2 0 0 30 15 0 0 30 15 6 | 5 dpdt1 0 0 30 15 0 0 30 15 7 | 6 connector 22 0 39 11 22 30 39 36 8 | 7 dc_voltage_supply 37 17 48 29 37 32 49 42 9 | 8 battery 37 17 48 29 37 32 49 42 10 | 9 dip4 10 2 50 11 10 47 50 57 11 | 10 dip6 10 2 50 11 10 67 50 77 12 | 11 dip8 10 2 50 11 10 87 50 97 13 | 12 dip10 10 2 50 11 10 107 50 117 14 | 13 dip12 10 2 50 11 10 127 50 137 15 | 14 dip14 10 2 50 11 10 147 50 157 16 | 15 dip16 10 2 50 11 10 167 50 177 17 | 16 dip8_opamp_single 10 2 50 11 10 87 50 97 18 | 17 dip8_opamp_dual 10 2 50 11 10 87 50 97 19 | 18 ldr 29 9 39 25 29 33 39 50 20 | 19 led 23 8 39 20 23 38 39 53 21 | 20 opamp_triangle_3pin 21 29 43 37 21 41 43 50 22 | 21 resistor 29 9 39 25 29 33 39 50 23 | 22 inductor 23 9 39 25 23 33 39 50 24 | 23 transistor_npn 1 1 15 15 1 23 15 39 25 | 24 transistor_pnp 1 1 15 15 1 23 15 39 26 | 25 transistor_npn_darlington 3 41 37 49 3 50 37 60 27 | 26 transistor_npn_darlington 3 41 37 49 3 50 37 60 28 | 27 universal 42 9 52 25 42 33 52 50 29 | 28 transistor_n_jfet 43 0 55 11 43 26 55 39 30 | 29 transistor_n_mosfet 43 0 55 11 43 26 55 39 31 | 30 transistor_p_mosfet 43 0 55 11 43 26 55 39 32 | 31 transistor_p_regulator 7 10 32 13 7 -8 32 3 33 | 32 transistor_n_regulator 7 10 32 13 7 -8 32 3 34 | 33 sinewave 29 9 39 25 29 33 39 50 35 | 34 ammeter 15 25 25 35 15 25 25 35 36 | 35 inductor_coupling 15 25 25 35 15 45 25 55 37 | -------------------------------------------------------------------------------- /js/schematic/guide.js: -------------------------------------------------------------------------------- 1 | // Net guide (extra point where the net should go) 2 | "use strict"; 3 | // globals: document, window, CA, vec2 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.Guide = function (aNet, aX, aY) { 8 | // Constructor 9 | this.net = aNet; 10 | this.x = aX || 0; 11 | this.y = aY || 0; 12 | }; 13 | 14 | SC.Guide.prototype.toObject = function () { 15 | // Return exportable object 16 | return { 17 | net: this.net, 18 | x: this.x, 19 | y: this.y 20 | }; 21 | }; 22 | 23 | SC.Guide.prototype.fromObject = function (aObject) { 24 | // Set this from previously exported object 25 | this.net = aObject.net; 26 | this.x = aObject.x; 27 | this.y = aObject.y; 28 | }; 29 | 30 | SC.Guide.prototype.render = function (aContext) { 31 | // Render Guide 32 | if (!SC.show.guides) { 33 | return; 34 | } 35 | var a = SC.v.canvasToScreen(this.x, this.y), 36 | size = 4 * SC.v.zoom / 20, 37 | s2 = size / 2; 38 | // dot 39 | aContext.globalAlpha = 1; 40 | aContext.fillStyle = '#00ff0099'; 41 | aContext.fillRect(a.x - s2, a.y - s2, size, size); 42 | // net id 43 | if (SC.show.net_numbers) { 44 | aContext.fillStyle = 'green'; 45 | aContext.textBaseline = 'middle'; 46 | aContext.textAlign = 'left'; 47 | aContext.font = '10px sans-serif'; 48 | aContext.fillText(this.net, a.x + s2, a.y); 49 | } 50 | // selection 51 | if (this === SC.selected) { 52 | aContext.strokeStyle = 'red'; 53 | aContext.strokeRect(a.x - s2, a.y - s2, size, size); 54 | } 55 | }; 56 | 57 | -------------------------------------------------------------------------------- /js/schematic/netlist_import_pedalgen.js: -------------------------------------------------------------------------------- 1 | // Import pedalgen parameters from url 2 | // linter: ngspicejs-lint 3 | /* global window, URLSearchParams, document, console, CA */ 4 | "use strict"; 5 | 6 | var SC = window.SC || {}; 7 | 8 | SC.netlistImportPedalgenFromUrl = function () { 9 | // Import netlist directly from the url: 10 | // schematic.html?pedalgen=battery,U1,9,1,0|resistor,R1,100,1,2|resistor,R2,1M,2,0 11 | var s = (new URLSearchParams(document.location.search)).get('pedalgen'); 12 | if (!s) { 13 | return; 14 | } 15 | var a = s.split('|').map((a) => a.split(',')); 16 | var ret = []; 17 | a.forEach((c) => { 18 | var o = { 19 | type: c[0], 20 | name: c[1], 21 | value: c[2], 22 | nets: c.slice(3) 23 | }; 24 | if (o.type === 'npn' || o.type === 'pnp') { 25 | o.type = 'transistor_' + o.type; 26 | } 27 | o.nets = o.nets.map(function (n) { 28 | return n === '0' ? 'gnd' : n; 29 | }); 30 | ret.push(o); 31 | }); 32 | var d = CA.modalDialog('Import pedalgen netlist?', s.split('|').join('\n'), ['Import', 'Cancel'], function (aButton) { 33 | if (aButton === 'Import') { 34 | SC.netlistImport(ret); // , SC.components.toObject() 35 | SC.filename = 'pedalgen'; 36 | SC.v.zoom = 20; 37 | var c = SC.components.center(); 38 | SC.v.centerTo(c.x, c.y); 39 | SC.render(); 40 | SC.onTool({target:{id: 'tool_select'}}); 41 | } 42 | }); 43 | d.content.style.whiteSpace = 'pre-wrap'; 44 | console.log(ret); 45 | }; 46 | 47 | 48 | -------------------------------------------------------------------------------- /js/stripboard/properties_dialog.js: -------------------------------------------------------------------------------- 1 | // Standard properties dialog for name and value, covers 90% of cases 2 | "use strict"; 3 | // globals: document, window, CA 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.propertiesDialog = function (aComponent, aUnits, aCallback, aValidator) { 8 | // Standard properties dialog for name and value 9 | var dlg, name, value; 10 | 11 | dlg = CA.modalDialog(aComponent.type, '', ['Save', 'Cancel'], function (aButton) { 12 | if (aButton === 'Save') { 13 | if (aComponent.hasOwnProperty('value')) { 14 | aComponent.value = value.input.value; 15 | } 16 | if (aComponent.hasOwnProperty('name')) { 17 | aComponent.name = name.input.value; 18 | } 19 | } 20 | SC.render(); 21 | if (aCallback) { 22 | return aCallback(aButton, aComponent); 23 | } 24 | }); 25 | 26 | // value 27 | if (aComponent.hasOwnProperty('value')) { 28 | value = CA.labelInput(dlg.content, 'Value', 'text', aUnits); 29 | value.input.value = aComponent.value; 30 | value.input.focus(); 31 | value.input.select(); 32 | } 33 | 34 | // name 35 | if (aComponent.hasOwnProperty('name')) { 36 | name = CA.labelInput(dlg.content, 'Name', 'text', ' '); 37 | name.input.value = aComponent.name; 38 | if (!value) { 39 | name.input.focus(); 40 | name.input.select(); 41 | } 42 | } 43 | 44 | // show it near top not center 45 | dlg.div.style.position = 'fixed'; 46 | dlg.div.style.top = '1cm'; 47 | 48 | return { 49 | dlg: dlg, 50 | value: value, 51 | name: name 52 | }; 53 | }; 54 | 55 | -------------------------------------------------------------------------------- /css/sidebar.css: -------------------------------------------------------------------------------- 1 | /* sidebar */ 2 | 3 | #sidebar { 4 | height: calc(100% - 16px); 5 | overflow-y: auto; 6 | box-sizing: border-box; 7 | background-color: silver; 8 | display: flex; 9 | flex-direction: column; 10 | z-index: 10; 11 | padding: 0.5ex; 12 | box-shadow: 0 0 1ex; 13 | font-size: small; 14 | user-select: none; 15 | } 16 | #sidebar .section { 17 | color: black; 18 | text-align: center; 19 | font-weight: bold; 20 | padding-top: 1ex; 21 | } 22 | #sidebar button, #sidebar label { 23 | text-align: left; 24 | background-color: transparent; 25 | background-size: contain; 26 | background-repeat: no-repeat; 27 | background-position: center; 28 | border: none; 29 | outline: none; 30 | font-size: small; 31 | } 32 | #sidebar input[type=checkbox] { 33 | vertical-align: middle; 34 | } 35 | #sidebar button.selected { 36 | background-color: lime; 37 | } 38 | #sidebar button:hover, #sidebar label:hover { 39 | background-color: skyblue; 40 | } 41 | #sidebar button[disabled] { 42 | text-align: center; 43 | } 44 | #sidebar button[disabled]:hover { 45 | background-color: transparent; 46 | } 47 | #sidebar button:active, #sidebar button.selected2 { 48 | background-color: lime; 49 | } 50 | #sidebar .two_columns_OFF { 51 | display: flex; 52 | flex-direction: column; 53 | } 54 | #sidebar .two_columns { 55 | width: 200px; 56 | column-count: 2; 57 | column-gap: 0; 58 | } 59 | #sidebar .two_columns button { 60 | white-space: nowrap; 61 | width: 100%; 62 | display: block; 63 | } 64 | #sidebar .bottom { 65 | flex: 1; 66 | display: flex; 67 | align-items: flex-end; 68 | justify-content: center; 69 | gap: 1ex; 70 | } 71 | -------------------------------------------------------------------------------- /js/schematic/label.js: -------------------------------------------------------------------------------- 1 | // Optimally render labels for components 2 | "use strict"; 3 | // globals: document, window, CA 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.renderLabel = SC.renderLabel || {}; 8 | 9 | SC.renderLabelUnits = { 10 | dc_voltage_supply: 'V' 11 | }; 12 | 13 | SC.renderLabel.universal = function (aContext, aComponent) { 14 | // Render name and value 15 | var z = SC.v.zoom / SC.gridSize, 16 | e = aComponent, 17 | fs = 12, 18 | p = SC.v.canvasToScreen(aComponent.x, aComponent.y), 19 | r; 20 | aContext.globalAlpha = 1; 21 | // boxes for name and value 22 | r = SC.anchorBoxes(e); 23 | // no anchor, use universal anchor 24 | if (!r) { 25 | r = SC.anchorBoxes(e, SC.specsAnchor[aComponent.type] || SC.specsAnchor.universal); 26 | } 27 | if (aComponent.type === 'opamp_triangle_3pin') { 28 | fs = 10; 29 | } 30 | //console.log(aComponent); 31 | aContext.font = Math.floor(fs * z) + 'px sans-serif'; 32 | aContext.fillStyle = 'black'; 33 | // name 34 | aContext.textAlign = r.a.textAlign; 35 | aContext.textBaseline = r.a.textBaseline; 36 | aContext.fillText(e.name, p.x + z * r.a.textX, p.y + r.a.textY * z); 37 | // value 38 | if (e.value !== undefined) { 39 | aContext.textAlign = r.b.textAlign; 40 | aContext.textBaseline = r.b.textBaseline; 41 | aContext.fillText(e.value + (SC.renderLabelUnits[aComponent.type] || ''), p.x + r.b.textX * z, p.y + r.b.textY * z); 42 | } 43 | /* 44 | console.log(r, aContext.font); 45 | aContext.strokeStyle = 'red'; 46 | aContext.strokeRect(p.x + r.a.x * z, p.y + r.a.y * z, r.a.w * z, r.a.h * z); 47 | aContext.strokeStyle = 'blue'; 48 | aContext.strokeRect(p.x + r.b.x * z, p.y + r.b.y * z, r.b.w * z, r.b.h * z); 49 | */ 50 | }; 51 | 52 | -------------------------------------------------------------------------------- /js/schematic/autoplace2.js: -------------------------------------------------------------------------------- 1 | // Attempt at better autoplacement of components after import 2 | "use strict"; 3 | // globals: document, window, CA 4 | /* global window, CA, console */ 5 | // linter: ngspicejs-lint 6 | 7 | var SC = window.SC || {}; 8 | 9 | SC.autoplace2 = function (aFixedNames, aConstraints) { 10 | console.log('autoplace2', aFixedNames, aConstraints); 11 | 12 | var best_score = 999999999, best, bestn; 13 | 14 | SC.autoplace2_randomize = function () { 15 | // Randomize components, render and return number of intersections 16 | SC.components.item.forEach((c) => { 17 | if (!aFixedNames[c.name]) { 18 | c.rotate = Math.floor(4 * Math.random()); 19 | c.mirror = Math.random() > 0.5; 20 | c.x = CA.randomInt(aConstraints.minx, aConstraints.maxx); 21 | c.y = CA.randomInt(aConstraints.miny, aConstraints.maxy); 22 | } 23 | c.updatePins(); 24 | }); 25 | //SC.components.render(SC.layer.component.context); 26 | SC.nets.render(); 27 | 28 | //SC.render(); 29 | return SC.nets.intersects; 30 | }; 31 | 32 | var t1 = Date.now(); 33 | for (var i = 0; i < 2000; i++) { 34 | var intersections = SC.autoplace2_randomize(); 35 | var total_length =SC.nets.total_length; 36 | var score = intersections * 10 + total_length; 37 | if (score < best_score) { 38 | best_score = score; 39 | best = SC.components.toObject(); 40 | bestn = SC.nets.toObject(); 41 | //console.log('best_score', Math.round(best_score), 'i', intersections, 'tl', total_length); 42 | } 43 | } 44 | 45 | SC.components.fromObject(best); 46 | SC.nets.fromObject(bestn); 47 | SC.render(); 48 | var t2 = Date.now(); 49 | console.log(t2 - t1, 'ms'); 50 | }; 51 | -------------------------------------------------------------------------------- /js/schematic/netlist_import.js: -------------------------------------------------------------------------------- 1 | // Import netlist made by stripboard.html 2 | "use strict"; 3 | // globals: document, window 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.netlistImport = function (aData, aOldComponents, aOldGuides) { 8 | // Import netlist made by stripboard.html 9 | var i, c, p, ok, has_position = false; 10 | SC.components.item = []; 11 | SC.nets.item = []; 12 | SC.guides.item = []; 13 | // components 14 | for (i = 0; i < aData.length; i++) { 15 | ok = false; 16 | // import special by schematic (e.g. 2 triangles instead of dip8 dual opamp) 17 | if (SC.netlistImportSchematic[aData[i].schematic]) { 18 | ok = SC.netlistImportSchematic[aData[i].schematic](aData[i], aOldComponents); 19 | c = ok; 20 | } 21 | // normal import 22 | if (!ok) { 23 | c = new SC.Component(aData[i].type, aData[i].name, aData[i].value, 0, 0, 0, false); 24 | SC.components.item.push(c); 25 | for (p = 0; p < aData[i].nets.length; p++) { 26 | SC.nets.addComponentPin(aData[i].nets[p], c, p); 27 | } 28 | } 29 | // use position if provided 30 | if (aData[i].hasOwnProperty('x')) { 31 | c.x = aData[i].x; 32 | c.y = aData[i].y; 33 | c.rotate = aData[i].rotate; 34 | c.mirror = aData[i].mirror; 35 | c.updatePins(); 36 | has_position = true; 37 | //console.log(aData[i], c); 38 | } 39 | } 40 | // autoplace 41 | if (!has_position) { 42 | SC.autoplaceByType(); 43 | } 44 | // reuse old position/rotation/mirror if available 45 | if (aOldComponents) { 46 | for (i = 0; i < SC.components.item.length; i++) { 47 | SC.components.item[i].restoreFromComponents(aOldComponents); 48 | } 49 | } 50 | // restore guides if available 51 | if (aOldGuides) { 52 | SC.render(); 53 | for (i = 0; i < aOldGuides.item.length; i++) { 54 | SC.guides.item.push(new SC.Guide(aOldGuides.item[i].net, aOldGuides.item[i].x, aOldGuides.item[i].y)); 55 | } 56 | return true; 57 | } 58 | }; 59 | 60 | -------------------------------------------------------------------------------- /js/stripboard/component_resistor.js: -------------------------------------------------------------------------------- 1 | // Resistor 2 | "use strict"; 3 | // globals: document, window, CA, vec2 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.Resistor = function (aName, aX1, aY1, aX2, aY2) { 8 | this.type = 'resistor'; 9 | this.name = aName; 10 | this.x1 = aX1 || 0; 11 | this.y1 = aY1 || 0; 12 | this.x2 = aX2 || 0; 13 | this.y2 = aY2 || 0; 14 | this.value = '1k'; 15 | }; 16 | 17 | SC.factory.resistor = SC.Resistor; 18 | SC.namingPrefix.resistor = 'R'; 19 | 20 | SC.Resistor.prototype.toObject = function () { 21 | // Return exportable object 22 | return { 23 | type: this.type, 24 | name: this.name, 25 | x1: this.x1, 26 | y1: this.y1, 27 | x2: this.x2, 28 | y2: this.y2, 29 | value: this.value 30 | }; 31 | }; 32 | 33 | SC.Resistor.prototype.fromObject = function (aObject) { 34 | // Set this from previously exported object 35 | this.type = aObject.type; 36 | this.name = aObject.name; 37 | this.x1 = aObject.x1; 38 | this.y1 = aObject.y1; 39 | this.x2 = aObject.x2; 40 | this.y2 = aObject.y2; 41 | this.value = aObject.value; 42 | }; 43 | 44 | SC.Resistor.prototype.toNetlist = function () { 45 | // Return netlist object 46 | return { 47 | type: this.type, 48 | name: this.name, 49 | value: this.value, 50 | nets: this.nets() 51 | }; 52 | }; 53 | 54 | SC.Resistor.prototype.nets = function () { 55 | // Return nets connected to pins 56 | return [ 57 | SC.grid.net(this.x1, this.y1), 58 | SC.grid.net(this.x2, this.y2) 59 | ]; 60 | }; 61 | 62 | SC.Resistor.prototype.hasPin = function (aX, aY) { 63 | // Return true if given coords are at any of the pins 64 | return (this.x1 === aX && this.y1 === aY) || (this.x2 === aX && this.y2 === aY); 65 | }; 66 | 67 | SC.Resistor.prototype.distanceTo = function (aX, aY) { 68 | // Return distance to given point on grid 69 | return CA.distancePointLineSegment(aX, aY, this.x1, this.y1, this.x2, this.y2); 70 | }; 71 | 72 | SC.Resistor.prototype.render = function (aContext) { 73 | // Render component 74 | SC.renderDefault(aContext, this.x1, this.y1, this.x2, this.y2, SC.image.resistor, this.name, this.value, 49, 24); 75 | }; 76 | 77 | SC.Resistor.prototype.propertiesDialog = function (aCallback) { 78 | // Show properties dialog 79 | return SC.propertiesDialog(this, 'Ω', aCallback); 80 | }; 81 | 82 | -------------------------------------------------------------------------------- /js/stripboard/component_led.js: -------------------------------------------------------------------------------- 1 | // LED 2 | "use strict"; 3 | // globals: document, window, CA, vec2 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.Led = function (aName, aX1, aY1, aX2, aY2) { 8 | this.type = 'led'; 9 | this.name = aName; 10 | this.x1 = aX1 || 0; 11 | this.y1 = aY1 || 0; 12 | this.x2 = aX2 || 0; 13 | this.y2 = aY2 || 0; 14 | this.value = 'green'; 15 | }; 16 | 17 | SC.factory.led = SC.Led; 18 | SC.namingPrefix.led = 'D'; 19 | 20 | SC.Led.prototype.toObject = function () { 21 | // Return exportable object 22 | return { 23 | type: this.type, 24 | name: this.name, 25 | x1: this.x1, 26 | y1: this.y1, 27 | x2: this.x2, 28 | y2: this.y2, 29 | value: this.value 30 | }; 31 | }; 32 | 33 | SC.Led.prototype.fromObject = function (aObject) { 34 | // Set this from previously exported object 35 | this.type = aObject.type; 36 | this.name = aObject.name; 37 | this.x1 = aObject.x1; 38 | this.y1 = aObject.y1; 39 | this.x2 = aObject.x2; 40 | this.y2 = aObject.y2; 41 | this.value = aObject.value; 42 | }; 43 | 44 | SC.Led.prototype.toNetlist = function () { 45 | // Return netlist object 46 | return { 47 | type: this.type, 48 | name: this.name, 49 | value: this.value, 50 | nets: this.nets() 51 | }; 52 | }; 53 | 54 | SC.Led.prototype.nets = function () { 55 | // Return nets connected to pins 56 | return [ 57 | SC.grid.net(this.x1, this.y1), 58 | SC.grid.net(this.x2, this.y2) 59 | ]; 60 | }; 61 | 62 | SC.Led.prototype.flip = function () { 63 | // Flip pins (polarity) 64 | [this.x1, this.y1, this.x2, this.y2] = [this.x2, this.y2, this.x1, this.y1]; 65 | }; 66 | 67 | SC.Led.prototype.hasPin = function (aX, aY) { 68 | // Return true if given coords are at any of the pins 69 | return (this.x1 === aX && this.y1 === aY) || (this.x2 === aX && this.y2 === aY); 70 | }; 71 | 72 | SC.Led.prototype.distanceTo = function (aX, aY) { 73 | // Return distance to given point on grid 74 | return CA.distancePointLineSegment(aX, aY, this.x1, this.y1, this.x2, this.y2); 75 | }; 76 | 77 | SC.Led.prototype.render = function (aContext) { 78 | // Render component 79 | SC.renderDefault(aContext, this.x1, this.y1, this.x2, this.y2, SC.image.led, this.name, this.value, 49, 32); 80 | }; 81 | 82 | SC.Led.prototype.propertiesDialog = function (aCallback) { 83 | // Show properties dialog 84 | return SC.propertiesDialog(this, '', aCallback); 85 | }; 86 | 87 | -------------------------------------------------------------------------------- /js/stripboard/component_ldr.js: -------------------------------------------------------------------------------- 1 | // LDR resistor 2 | "use strict"; 3 | // globals: document, window, CA, vec2 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.Ldr = function (aName, aX1, aY1, aX2, aY2) { 8 | this.type = 'ldr'; 9 | this.name = aName; 10 | this.x1 = aX1 || 0; 11 | this.y1 = aY1 || 0; 12 | this.x2 = aX2 || 0; 13 | this.y2 = aY2 || 0; 14 | this.value = ''; 15 | }; 16 | 17 | SC.factory.ldr = SC.Ldr; 18 | SC.namingPrefix.ldr = 'LDR'; 19 | 20 | SC.Ldr.prototype.toObject = function () { 21 | // Return exportable object 22 | return { 23 | type: this.type, 24 | name: this.name, 25 | x1: this.x1, 26 | y1: this.y1, 27 | x2: this.x2, 28 | y2: this.y2, 29 | value: this.value 30 | }; 31 | }; 32 | 33 | SC.Ldr.prototype.fromObject = function (aObject) { 34 | // Set this from previously exported object 35 | this.type = aObject.type; 36 | this.name = aObject.name; 37 | this.x1 = aObject.x1; 38 | this.y1 = aObject.y1; 39 | this.x2 = aObject.x2; 40 | this.y2 = aObject.y2; 41 | this.value = aObject.value; 42 | }; 43 | 44 | SC.Ldr.prototype.toNetlist = function () { 45 | // Return netlist object 46 | return { 47 | type: this.type, 48 | name: this.name, 49 | value: this.value, 50 | nets: this.nets() 51 | }; 52 | }; 53 | 54 | SC.Ldr.prototype.nets = function () { 55 | // Return nets connected to pins 56 | return [ 57 | SC.grid.net(this.x1, this.y1), 58 | SC.grid.net(this.x2, this.y2) 59 | ]; 60 | }; 61 | 62 | SC.Ldr.prototype.flip = function () { 63 | // Flip pins (polarity) 64 | [this.x1, this.y1, this.x2, this.y2] = [this.x2, this.y2, this.x1, this.y1]; 65 | }; 66 | 67 | SC.Ldr.prototype.hasPin = function (aX, aY) { 68 | // Return true if given coords are at any of the pins 69 | return (this.x1 === aX && this.y1 === aY) || (this.x2 === aX && this.y2 === aY); 70 | }; 71 | 72 | SC.Ldr.prototype.distanceTo = function (aX, aY) { 73 | // Return distance to given point on grid 74 | return CA.distancePointLineSegment(aX, aY, this.x1, this.y1, this.x2, this.y2); 75 | }; 76 | 77 | SC.Ldr.prototype.render = function (aContext) { 78 | // Render component 79 | SC.renderDefault(aContext, this.x1, this.y1, this.x2, this.y2, SC.image.ldr, this.name, this.value, 49, 24); 80 | }; 81 | 82 | SC.Ldr.prototype.propertiesDialog = function (aCallback) { 83 | // Show properties dialog 84 | return SC.propertiesDialog(this, '', aCallback); 85 | }; 86 | 87 | -------------------------------------------------------------------------------- /js/stripboard/component_diode.js: -------------------------------------------------------------------------------- 1 | // Diode 2 | "use strict"; 3 | // globals: document, window, CA, vec2 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.Diode = function (aName, aX1, aY1, aX2, aY2) { 8 | this.type = 'diode'; 9 | this.name = aName; 10 | this.x1 = aX1 || 0; 11 | this.y1 = aY1 || 0; 12 | this.x2 = aX2 || 0; 13 | this.y2 = aY2 || 0; 14 | this.value = '1N4148'; 15 | }; 16 | 17 | SC.factory.diode = SC.Diode; 18 | SC.namingPrefix.diode = 'D'; 19 | 20 | SC.Diode.prototype.toObject = function () { 21 | // Return exportable object 22 | return { 23 | type: this.type, 24 | name: this.name, 25 | x1: this.x1, 26 | y1: this.y1, 27 | x2: this.x2, 28 | y2: this.y2, 29 | value: this.value 30 | }; 31 | }; 32 | 33 | SC.Diode.prototype.fromObject = function (aObject) { 34 | // Set this from previously exported object 35 | this.type = aObject.type; 36 | this.name = aObject.name; 37 | this.x1 = aObject.x1; 38 | this.y1 = aObject.y1; 39 | this.x2 = aObject.x2; 40 | this.y2 = aObject.y2; 41 | this.value = aObject.value; 42 | }; 43 | 44 | SC.Diode.prototype.toNetlist = function () { 45 | // Return netlist object 46 | return { 47 | type: this.type, 48 | name: this.name, 49 | value: this.value, 50 | nets: this.nets() 51 | }; 52 | }; 53 | 54 | SC.Diode.prototype.nets = function () { 55 | // Return nets connected to pins 56 | return [ 57 | SC.grid.net(this.x1, this.y1), 58 | SC.grid.net(this.x2, this.y2) 59 | ]; 60 | }; 61 | 62 | SC.Diode.prototype.flip = function () { 63 | // Flip pins (polarity) 64 | [this.x1, this.y1, this.x2, this.y2] = [this.x2, this.y2, this.x1, this.y1]; 65 | }; 66 | 67 | SC.Diode.prototype.hasPin = function (aX, aY) { 68 | // Return true if given coords are at any of the pins 69 | return (this.x1 === aX && this.y1 === aY) || (this.x2 === aX && this.y2 === aY); 70 | }; 71 | 72 | SC.Diode.prototype.distanceTo = function (aX, aY) { 73 | // Return distance to given point on grid 74 | return CA.distancePointLineSegment(aX, aY, this.x1, this.y1, this.x2, this.y2); 75 | }; 76 | 77 | SC.Diode.prototype.render = function (aContext) { 78 | // Render component 79 | SC.renderDefault(aContext, this.x1, this.y1, this.x2, this.y2, SC.image.diode, this.name, this.value, 49, 24); 80 | }; 81 | 82 | SC.Diode.prototype.propertiesDialog = function (aCallback) { 83 | // Show properties dialog 84 | return SC.propertiesDialog(this, '', aCallback); 85 | }; 86 | 87 | -------------------------------------------------------------------------------- /js/stripboard/component_inductor.js: -------------------------------------------------------------------------------- 1 | // Inductor 2 | "use strict"; 3 | // globals: document, window, CA, vec2 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.Inductor = function (aName, aX1, aY1, aX2, aY2) { 8 | this.type = 'inductor'; 9 | this.name = aName; 10 | this.x1 = aX1 || 0; 11 | this.y1 = aY1 || 0; 12 | this.x2 = aX2 || 0; 13 | this.y2 = aY2 || 0; 14 | this.value = '10m'; 15 | }; 16 | 17 | SC.factory.inductor = SC.Inductor; 18 | SC.namingPrefix.inductor = 'L'; 19 | 20 | SC.Inductor.prototype.toObject = function () { 21 | // Return exportable object 22 | return { 23 | type: this.type, 24 | name: this.name, 25 | x1: this.x1, 26 | y1: this.y1, 27 | x2: this.x2, 28 | y2: this.y2, 29 | value: this.value 30 | }; 31 | }; 32 | 33 | SC.Inductor.prototype.fromObject = function (aObject) { 34 | // Set this from previously exported object 35 | this.type = aObject.type; 36 | this.name = aObject.name; 37 | this.x1 = aObject.x1; 38 | this.y1 = aObject.y1; 39 | this.x2 = aObject.x2; 40 | this.y2 = aObject.y2; 41 | this.value = aObject.value; 42 | }; 43 | 44 | SC.Inductor.prototype.toNetlist = function () { 45 | // Return netlist object 46 | return { 47 | type: this.type, 48 | name: this.name, 49 | value: this.value, 50 | nets: this.nets() 51 | }; 52 | }; 53 | 54 | SC.Inductor.prototype.nets = function () { 55 | // Return nets connected to pins 56 | return [ 57 | SC.grid.net(this.x1, this.y1), 58 | SC.grid.net(this.x2, this.y2) 59 | ]; 60 | }; 61 | 62 | SC.Inductor.prototype.flip = function () { 63 | // Flip pins (polarity) 64 | [this.x1, this.y1, this.x2, this.y2] = [this.x2, this.y2, this.x1, this.y1]; 65 | }; 66 | 67 | SC.Inductor.prototype.hasPin = function (aX, aY) { 68 | // Return true if given coords are at any of the pins 69 | return (this.x1 === aX && this.y1 === aY) || (this.x2 === aX && this.y2 === aY); 70 | }; 71 | 72 | SC.Inductor.prototype.distanceTo = function (aX, aY) { 73 | // Return distance to given point on grid 74 | return CA.distancePointLineSegment(aX, aY, this.x1, this.y1, this.x2, this.y2); 75 | }; 76 | 77 | SC.Inductor.prototype.render = function (aContext) { 78 | // Render component 79 | SC.renderDefault(aContext, this.x1, this.y1, this.x2, this.y2, SC.image.inductor, this.name, this.value, 49, 15); 80 | }; 81 | 82 | SC.Inductor.prototype.propertiesDialog = function (aCallback) { 83 | // Show properties dialog 84 | return SC.propertiesDialog(this, 'H', aCallback); 85 | }; 86 | 87 | -------------------------------------------------------------------------------- /js/stripboard/component_capacitor.js: -------------------------------------------------------------------------------- 1 | // Capacitor 2 | "use strict"; 3 | // globals: document, window, CA, vec2 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.Capacitor = function (aName, aX1, aY1, aX2, aY2) { 8 | this.type = 'capacitor'; 9 | this.name = aName; 10 | this.x1 = aX1 || 0; 11 | this.y1 = aY1 || 0; 12 | this.x2 = aX2 || 0; 13 | this.y2 = aY2 || 0; 14 | this.value = '100n'; 15 | }; 16 | 17 | SC.factory.capacitor = SC.Capacitor; 18 | SC.namingPrefix.capacitor = 'C'; 19 | 20 | SC.Capacitor.prototype.toObject = function () { 21 | // Return exportable object 22 | return { 23 | type: this.type, 24 | name: this.name, 25 | x1: this.x1, 26 | y1: this.y1, 27 | x2: this.x2, 28 | y2: this.y2, 29 | value: this.value 30 | }; 31 | }; 32 | 33 | SC.Capacitor.prototype.fromObject = function (aObject) { 34 | // Set this from previously exported object 35 | this.type = aObject.type; 36 | this.name = aObject.name; 37 | this.x1 = aObject.x1; 38 | this.y1 = aObject.y1; 39 | this.x2 = aObject.x2; 40 | this.y2 = aObject.y2; 41 | this.value = aObject.value; 42 | }; 43 | 44 | SC.Capacitor.prototype.toNetlist = function () { 45 | // Return netlist object 46 | return { 47 | type: this.type, 48 | name: this.name, 49 | value: this.value, 50 | nets: this.nets() 51 | }; 52 | }; 53 | 54 | SC.Capacitor.prototype.nets = function () { 55 | // Return nets connected to pins 56 | return [ 57 | SC.grid.net(this.x1, this.y1), 58 | SC.grid.net(this.x2, this.y2) 59 | ]; 60 | }; 61 | 62 | SC.Capacitor.prototype.flip = function () { 63 | // Flip pins (polarity) 64 | [this.x1, this.y1, this.x2, this.y2] = [this.x2, this.y2, this.x1, this.y1]; 65 | }; 66 | 67 | SC.Capacitor.prototype.hasPin = function (aX, aY) { 68 | // Return true if given coords are at any of the pins 69 | return (this.x1 === aX && this.y1 === aY) || (this.x2 === aX && this.y2 === aY); 70 | }; 71 | 72 | SC.Capacitor.prototype.distanceTo = function (aX, aY) { 73 | // Return distance to given point on grid 74 | return CA.distancePointLineSegment(aX, aY, this.x1, this.y1, this.x2, this.y2); 75 | }; 76 | 77 | SC.Capacitor.prototype.render = function (aContext) { 78 | // Render component 79 | SC.renderDefault(aContext, this.x1, this.y1, this.x2, this.y2, SC.image.capacitor_blob, this.name, this.value, 49, 24); 80 | }; 81 | 82 | SC.Capacitor.prototype.propertiesDialog = function (aCallback) { 83 | // Show properties dialog 84 | return SC.propertiesDialog(this, 'F', aCallback); 85 | }; 86 | 87 | -------------------------------------------------------------------------------- /js/stripboard/render_default.js: -------------------------------------------------------------------------------- 1 | // Default component rendering (rotated image with extra leads) 2 | "use strict"; 3 | // globals: document, window, vec2, CA 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.renderDefault = function (aContext, aX1, aY1, aX2, aY2, aImage, aName, aValue, aPinX, aPinY) { 8 | // Render component 9 | var a = vec2(aX1, aY1), 10 | b = vec2(aX2, aY2), 11 | a2 = a.clone(), 12 | b2 = b.clone(), 13 | s = b.sub(a).unit(), 14 | angle, 15 | c = a.add(b).mul(0.5), 16 | length, 17 | length_between_pins; 18 | if (a2.distanceTo(b2) > 2) { 19 | a2 = c.add(s.mul(-1)); 20 | b2 = c.add(s.mul(1)); 21 | } 22 | // screen coords 23 | a = SC.grid.gridToScreen(a.x, a.y); 24 | a2 = SC.grid.gridToScreen(a2.x, a2.y); 25 | b = SC.grid.gridToScreen(b.x, b.y); 26 | b2 = SC.grid.gridToScreen(b2.x, b2.y); 27 | c = SC.grid.gridToScreen(c.x, c.y); 28 | length = CA.distance(a2.x, a2.y, b2.x, b2.y); 29 | angle = b.sub(a).angle(true) - Math.PI / 2, 30 | //console.log(aName, {a, b, c, a2, b2, length, angle}); 31 | // image 32 | aContext.globalAlpha = SC.show.components_opacity; 33 | length_between_pins = aImage.height - 2 * aPinY; 34 | aContext.ca_canvas.drawImageRotated( 35 | aImage, 36 | a2.x, 37 | a2.y, 38 | length * aImage.width / length_between_pins, 39 | length * aImage.height / length_between_pins, 40 | angle, 41 | aPinX, 42 | aPinY 43 | ); 44 | // dots 45 | aContext.fillStyle = '#0000ff'; 46 | aContext.beginPath(); 47 | aContext.arc(a.x, a.y, 4 * SC.v.zoom, 0, 2 * Math.PI, false); 48 | aContext.closePath(); 49 | aContext.arc(b.x, b.y, 4 * SC.v.zoom, 0, 2 * Math.PI, false); 50 | aContext.fill(); 51 | // extra leads 52 | aContext.lineWidth = 4 * SC.v.zoom; 53 | aContext.strokeStyle = '#000000'; 54 | aContext.beginPath(); 55 | aContext.moveTo(a.x, a.y); 56 | aContext.lineTo(a2.x, a2.y); 57 | aContext.stroke(); 58 | aContext.beginPath(); 59 | aContext.moveTo(b.x, b.y); 60 | aContext.lineTo(b2.x, b2.y); 61 | aContext.stroke(); 62 | // name + value 63 | aContext.globalAlpha = 1; 64 | aContext.font = 'bold 12px sans-serif'; 65 | aContext.textBaseline = 'bottom'; 66 | aContext.textAlign = 'center'; 67 | aContext.strokeStyle = 'black'; 68 | aContext.lineWidth = 2; 69 | aContext.fillStyle = 'white'; 70 | aContext.strokeText(aName, c.x, c.y); 71 | aContext.fillText(aName, c.x, c.y); 72 | aContext.textBaseline = 'top'; 73 | aContext.strokeText(aValue, c.x, c.y); 74 | aContext.fillText(aValue, c.x, c.y); 75 | }; 76 | 77 | -------------------------------------------------------------------------------- /js/stripboard/component_link.js: -------------------------------------------------------------------------------- 1 | // Link connecting 2 pieces of copper 2 | "use strict"; 3 | // globals: document, window, CA, vec2 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.Link = function (aName, aX1, aY1, aX2, aY2) { 8 | this.type = 'link'; 9 | this.name = aName; 10 | this.x1 = aX1 || 0; 11 | this.y1 = aY1 || 0; 12 | this.x2 = aX2 || 0; 13 | this.y2 = aY2 || 0; 14 | }; 15 | 16 | SC.factory.link = SC.Link; 17 | SC.namingPrefix.link = 'Link'; 18 | 19 | SC.Link.prototype.toObject = function () { 20 | // Return exportable object 21 | return { 22 | type: this.type, 23 | name: this.name, 24 | x1: this.x1, 25 | y1: this.y1, 26 | x2: this.x2, 27 | y2: this.y2 28 | }; 29 | }; 30 | 31 | SC.Link.prototype.fromObject = function (aObject) { 32 | // Set this from previously exported object 33 | this.type = aObject.type; 34 | this.name = aObject.name; 35 | this.x1 = aObject.x1; 36 | this.y1 = aObject.y1; 37 | this.x2 = aObject.x2; 38 | this.y2 = aObject.y2; 39 | }; 40 | 41 | SC.Link.prototype.toNetlist = function () { 42 | // Link defines which pieces of copper have same net id, link itself is not in netlist as a component 43 | return; 44 | }; 45 | 46 | SC.Link.prototype.hasPin = function (aX, aY) { 47 | // Return true if given coords are at any of the pins 48 | return (this.x1 === aX && this.y1 === aY) || (this.x2 === aX && this.y2 === aY); 49 | }; 50 | 51 | SC.Link.prototype.nets = function () { 52 | // Return nets connected to pins 53 | return [ 54 | SC.grid.net(this.x1, this.y1), 55 | SC.grid.net(this.x2, this.y2) 56 | ]; 57 | }; 58 | 59 | SC.Link.prototype.distanceTo = function (aX, aY) { 60 | // Return distance to given point on grid 61 | return CA.distancePointLineSegment(aX, aY, this.x1, this.y1, this.x2, this.y2); 62 | }; 63 | 64 | SC.Link.prototype.render = function (aContext) { 65 | // Render component 66 | var a = SC.grid.gridToScreen(this.x1, this.y1), 67 | b = SC.grid.gridToScreen(this.x2, this.y2); 68 | // dots 69 | aContext.fillStyle = '#0000ff77'; 70 | aContext.beginPath(); 71 | aContext.arc(a.x, a.y, 4 * SC.v.zoom, 0, 2 * Math.PI, false); 72 | aContext.closePath(); 73 | aContext.arc(b.x, b.y, 4 * SC.v.zoom, 0, 2 * Math.PI, false); 74 | aContext.fill(); 75 | // Link 76 | aContext.lineWidth = 4 * SC.v.zoom; 77 | aContext.strokeStyle = '#000000cc'; 78 | aContext.beginPath(); 79 | aContext.moveTo(a.x, a.y); 80 | aContext.lineTo(b.x, b.y); 81 | aContext.stroke(); 82 | }; 83 | 84 | SC.Link.prototype.propertiesDialog = function (aCallback) { 85 | // Link doesn't need dialog 86 | aCallback('Save'); 87 | }; 88 | 89 | -------------------------------------------------------------------------------- /data/stripboard_specs_transistor.tsv: -------------------------------------------------------------------------------- 1 | id model kind package pinout 2 | 1 BC547 NPN TO-92 CBE 3 | 2 AC128 PNP TO-1 CBE 4 | 3 2N3904 NPN TO-92 EBC 5 | 4 2N3906 PNP TO-92 EBC 6 | 5 2N2222 NPN TO-92 EBC 7 | 6 BD139 NPN TO-126 ECB 8 | 7 BD241C NPN TO-220 BCE 9 | 8 BC557 PNP TO-92 CBE 10 | 9 BC108 NPN TO-1 CBE 11 | 10 BC338 NPN TO-92 CBE 12 | 11 2N5401 PNP TO-92 EBC 13 | 12 2N5551 NPN TO-92 EBC 14 | 13 A42 NPN TO-92 EBC 15 | 14 A92 PNP TO-92 EBC 16 | 15 A733 PNP TO-92 ECB 17 | 16 A1015 PNP TO-92 ECB 18 | 17 C945 NPN TO-92 ECB 19 | 18 C1815 NPN TO-92 ECB 20 | 19 S8050 NPN TO-92 EBC 21 | 20 S8550 PNP TO-92 EBC 22 | 21 S9012 PNP TO-92 EBC 23 | 22 S9013 NPN TO-92 EBC 24 | 23 S9014 NPN TO-92 EBC 25 | 24 S9015 PNP TO-92 EBC 26 | 25 S9018 NPN TO-92 EBC 27 | 26 MPSA12 NPN_DARLINGTON TO-92 EBC 28 | 27 MPSA13 NPN_DARLINGTON TO-92 EBC 29 | 28 BC546 NPN TO-92 CBE 30 | 29 MPSA06 NPN TO-92 EBC 31 | 30 2N5088 NPN TO-92 EBC 32 | 31 2N5089 NPN TO-92 EBC 33 | 32 2N5306 NPN_DARLINGTON TO-92 EBC 34 | 33 BC549 NPN TO-92 CBE 35 | 34 2N1307 PNP TO-5 CBE 36 | 35 2N3391 NPN TO-92 ECB 37 | 36 BC184C NPN TO-92 CBE 38 | 37 BC169B NPN TO-92 ECB 39 | 38 BC550 NPN TO-92 CBE 40 | 39 BC559 PNP TO-92 CBE 41 | 40 BC560 PNP TO-92 CBE 42 | 41 BC337 NPN TO-92 CBE 43 | 42 BC239 NPN TO-92 CBE 44 | 43 BC109 NPN TO-1 CBE 45 | 44 TIP120 NPN_DARLINGTON TO-220 BCE 46 | 45 2N2925 NPN TO-92 EBC 47 | 46 AC176 NPN TO-5 CBE 48 | 47 MPS8098 NPN TO-92 EBC 49 | 48 BC213L PNP TO-92 EBC 50 | 49 BC183L NPN TO-92 ECB 51 | 50 BD136 PNP TO-126 ECB 52 | 51 ZTX705 PNP_DARLINGTON TO-92 EBC 53 | 52 IRF540N N_MOSFET TO-220 GDS 54 | 53 IRF5305 P_MOSFET TO-220 GDS 55 | 54 IRLZ44N N_MOSFET TO-220 GDS 56 | 55 BS170 N_MOSFET TO-92 DGS 57 | 56 MPSA18 NPN TO-92 EBC 58 | 57 2N4401 NPN TO-92 EBC 59 | 58 2N4403 PNP TO-92 EBC 60 | 59 2N4402 PNP TO-92 CBE 61 | 60 2N4125 PNP TO-92 EBC 62 | 61 2N2907 PNP TO-92 CBE 63 | 62 J201 N_JFET TO-92 DSG 64 | 63 MPF102 N_JFET TO-92 DSG 65 | 64 2N5457 N_JFET TO-92 DSG 66 | 65 2N5952 N_JFET TO-92 GSD 67 | 66 BF245A N_JFET TO-92 GSD 68 | 67 2SK30A N_JFET TO-92 SGD 69 | 68 2SK246 N_JFET TO-92 SGD 70 | 69 2SC536 NPN TO-92 ECB 71 | 70 78L33 P_REGULATOR TO-92 OGI 72 | 71 78L05 P_REGULATOR TO-92 OGI 73 | 72 78L06 P_REGULATOR TO-92 OGI 74 | 73 78L08 P_REGULATOR TO-92 OGI 75 | 74 78L09 P_REGULATOR TO-92 OGI 76 | 75 78L12 P_REGULATOR TO-92 OGI 77 | 76 78L15 P_REGULATOR TO-92 OGI 78 | 77 79L05 N_REGULATOR TO-92 GIO 79 | 78 79L06 N_REGULATOR TO-92 GIO 80 | 79 79L08 N_REGULATOR TO-92 GIO 81 | 80 79L09 N_REGULATOR TO-92 GIO 82 | 81 79L12 N_REGULATOR TO-92 GIO 83 | 82 79L15 N_REGULATOR TO-92 GIO 84 | 83 L7805 P_REGULATOR TO-220 IGO 85 | 84 L7806 P_REGULATOR TO-220 IGO 86 | 85 L7808 P_REGULATOR TO-220 IGO 87 | 86 L7809 P_REGULATOR TO-220 IGO 88 | 87 L7812 P_REGULATOR TO-220 IGO 89 | 88 L7815 P_REGULATOR TO-220 IGO 90 | 89 L7905 N_REGULATOR TO-220 GIO 91 | 90 L7906 N_REGULATOR TO-220 GIO 92 | 91 L7908 N_REGULATOR TO-220 GIO 93 | 92 L7909 N_REGULATOR TO-220 GIO 94 | 93 L7912 N_REGULATOR TO-220 GIO 95 | 94 L7915 N_REGULATOR TO-220 GIO 96 | 95 LM317 P_REGULATOR TO-220 GOI 97 | 96 NKT213 PNP TO-1 EBC 98 | 97 NKT275 PNP TO-1 EBC 99 | 98 OC71 PNP TO-1 EBC 100 | 99 OC44 PNP TO-1 EBC 101 | 100 OC45 PNP TO-1 EBC 102 | 101 OC78 PNP TO-1 EBC 103 | 102 OC81D PNP TO-1 EBC 104 | 103 OC81 PNP TO-1 EBC -------------------------------------------------------------------------------- /data/specs_tsv_to_js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nodejs 2 | 3 | // Read transistor.tsv and convert it to transistor.js 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | 7 | var t = fs.readFileSync('stripboard_specs_transistor.tsv', 'utf8').trim().split('\n'); 8 | var i, c, o = {}; 9 | for (i = 1; i < t.length; i++) { 10 | c = t[i].split('\t'); 11 | o[c[1]] = { 12 | kind: c[2], 13 | package: c[3], 14 | pinout: c[4] 15 | }; 16 | } 17 | fs.writeFileSync( 18 | '../js/stripboard/specs_transistor.js', 19 | '// Transistor specifications\n// This file was generated by data/specs_tsv_to_js\n\nSC.specsTransistor = ' + JSON.stringify(o, undefined, 4) + ';' 20 | ); 21 | 22 | // DIP info 23 | var size; 24 | t = fs.readFileSync('stripboard_specs_dip.tsv', 'utf8').trim().split('\n'); 25 | o = {}; 26 | for (i = 1; i < t.length; i++) { 27 | c = t[i].split('\t'); 28 | size = c[2]; 29 | o[size] = o[size] || {}; 30 | o[size][c[1]] = { 31 | pinout: c[3], //.split(','), 32 | schematic: c[4] //.split(',') 33 | }; 34 | } 35 | fs.writeFileSync( 36 | '../js/stripboard/specs_dip.js', 37 | '// DIP specifications\n// This file was generated by data/specs_tsv_to_js\n\nSC.specsDip = ' + JSON.stringify(o, undefined, 4) + ';' 38 | ); 39 | 40 | // Package pin origin 41 | t = fs.readFileSync('stripboard_package.tsv', 'utf8').trim().split('\n'); 42 | o = {}; 43 | for (i = 1; i < t.length; i++) { 44 | c = t[i].split('\t'); 45 | size = c[1]; 46 | o[size] = { 47 | x: parseFloat(c[2]), 48 | y: parseFloat(c[3]) 49 | }; 50 | } 51 | fs.writeFileSync( 52 | '../js/stripboard/specs_package.js', 53 | '// Package pin offset specifications\n// This file was generated by data/specs_tsv_to_js\n\nSC.specsPackage = ' + JSON.stringify(o, undefined, 4) + ';' 54 | ); 55 | 56 | // Anchors 57 | var size; 58 | t = fs.readFileSync('schematic_specs_anchor.tsv', 'utf8').trim().split('\n'); 59 | o = {}; 60 | for (i = 1; i < t.length; i++) { 61 | c = t[i].split('\t'); 62 | o[c[1]] = { 63 | name: {x1: c[2], y1: c[3], x2: c[4], y2: c[5]}, 64 | value: {x1: c[6], y1: c[7], x2: c[8], y2: c[9]} 65 | }; 66 | } 67 | fs.writeFileSync( 68 | '../js/schematic/specs_anchor.js', 69 | '// Component name/value label placement anchors\n// This file was generated by data/specs_tsv_to_js\n\nSC.specsAnchor = ' + JSON.stringify(o, undefined, 4) + ';' 70 | ); 71 | 72 | // Pin 73 | var name, pin, x, y, a = []; 74 | t = fs.readFileSync('schematic_specs_pin.tsv', 'utf8').trim().split('\n'); 75 | o = {}; 76 | for (i = 1; i < t.length; i++) { 77 | c = t[i].split('\t'); 78 | name = c[1]; 79 | pin = c[2]; 80 | x = parseInt(c[3], 10); 81 | y = parseInt(c[4], 10); 82 | if (i === t.length) { 83 | if (!Number.isNaN(x)) { 84 | a.push({x: x, y: y, name: pin}); 85 | } 86 | break; 87 | } 88 | if (name !== '' || i === t.length - 1) { 89 | a = []; 90 | o[name] = a; 91 | } 92 | if (!Number.isNaN(x)) { 93 | a.push({x: x, y: y, name: pin}); 94 | } 95 | } 96 | fs.writeFileSync( 97 | '../js/schematic/specs_pin.js', 98 | '// Component pins specification\n// This file was generated by data/specs_tsv_to_js\n\nSC.specsPin = ' + JSON.stringify(o, undefined, 4) + ';' 99 | ); 100 | -------------------------------------------------------------------------------- /data/schematic_specs_pin.tsv: -------------------------------------------------------------------------------- 1 | id type pin x y 2 | 1 resistor A 20 0 3 | 2 B 20 60 4 | 3 sinewave A 20 0 5 | 4 B 20 60 6 | 5 ldr A 20 0 7 | 6 B 20 60 8 | 7 potentiometer Start 20 60 9 | 8 Wiper 0 40 10 | 9 End 20 0 11 | 10 spst 1 20 0 12 | 11 2 20 60 13 | 12 spdt 1 20 0 14 | 13 2 20 60 15 | 14 3 40 60 16 | 15 dpdt1 1 0 20 17 | 16 2 0 40 18 | 17 3 0 60 19 | 18 4 60 60 20 | 19 5 60 40 21 | 20 6 60 20 22 | 21 dpdt2 1 60 20 23 | 22 2 0 40 24 | 23 3 60 40 25 | 24 4 60 80 26 | 25 5 0 80 27 | 26 6 60 60 28 | 27 capacitor A 20 0 29 | 28 B 20 60 30 | 29 capacitor_pol A 20 0 31 | 30 B 20 60 32 | 31 diode A 20 0 33 | 32 B 20 60 34 | 33 led A 20 0 35 | 34 B 20 60 36 | 35 connector A 0 20 37 | 36 ground GND 20 0 38 | 37 dc_voltage_supply P 20 0 39 | 38 N 20 60 40 | 39 battery P 20 0 41 | 40 N 20 60 42 | 41 transistor_n_jfet D 40 0 43 | 42 G 0 20 44 | 43 S 40 40 45 | 44 transistor_n_mosfet D 40 0 46 | 45 G 0 20 47 | 46 S 40 40 48 | 47 transistor_p_mosfet D 40 0 49 | 48 G 0 20 50 | 49 S 40 40 51 | 50 transistor_p_jfet D 40 0 52 | 51 G 0 20 53 | 52 S 40 40 54 | 53 transistor_npn C 40 0 55 | 54 B 0 20 56 | 55 E 40 40 57 | 56 transistor_npn_darlington C 60 0 58 | 57 B 0 20 59 | 58 E 60 60 60 | 59 transistor_pnp C 40 0 61 | 60 B 0 20 62 | 61 E 40 40 63 | 62 transistor_pnp_darlington C 60 60 64 | 63 B 0 40 65 | 64 E 60 0 66 | 65 dip8 P1 0 20 67 | 66 P2 0 40 68 | 67 P3 0 60 69 | 68 P4 0 80 70 | 69 P5 60 80 71 | 70 P6 60 60 72 | 71 P7 60 40 73 | 72 P8 60 20 74 | 73 dip8_opamp_single OFS1 0 20 75 | 74 IN- 0 40 76 | 75 IN+ 0 60 77 | 76 V- 0 80 78 | 77 OFS2 60 80 79 | 78 OUT 60 60 80 | 79 V+ 60 40 81 | 80 NC1 60 20 82 | 81 dip8_opamp_dual P1 0 20 83 | 82 P2 0 40 84 | 83 P3 0 60 85 | 84 P4 0 80 86 | 85 P5 60 80 87 | 86 P6 60 60 88 | 87 P7 60 40 89 | 88 P8 60 20 90 | 89 opamp_triangle_3pin IN+ 0 20 91 | 90 IN- 0 60 92 | 91 OUT 80 40 93 | 92 transistor_p_regulator I 0 20 94 | 93 G 20 40 95 | 94 O 40 20 96 | 95 transistor_n_regulator I 0 20 97 | 96 G 20 40 98 | 97 O 40 20 99 | 98 inductor A 20 0 100 | 99 B 20 60 101 | 100 dip4 P1 0 20 102 | 101 P2 0 40 103 | 102 P3 60 40 104 | 103 P4 60 20 105 | 104 dip6 P1 0 20 106 | 105 P2 0 40 107 | 106 P3 0 60 108 | 107 P4 60 60 109 | 108 P5 60 40 110 | 109 P6 60 20 111 | 110 dip10 P1 0 20 112 | 111 P2 0 40 113 | 112 P3 0 60 114 | 113 P4 0 80 115 | 114 P5 0 100 116 | 115 P6 60 100 117 | 116 P7 60 80 118 | 117 P8 60 60 119 | 118 P9 60 40 120 | 119 P10 60 20 121 | 120 dip12 P1 0 20 122 | 121 P2 0 40 123 | 122 P3 0 60 124 | 123 P4 0 80 125 | 124 P5 0 100 126 | 125 P6 0 120 127 | 126 P7 60 120 128 | 127 P8 60 100 129 | 128 P9 60 80 130 | 129 P10 60 60 131 | 130 P11 60 40 132 | 131 P12 60 20 133 | 132 dip14 P1 0 20 134 | 133 P2 0 40 135 | 134 P3 0 60 136 | 135 P4 0 80 137 | 136 P5 0 100 138 | 137 P6 0 120 139 | 138 P7 0 140 140 | 139 P8 60 140 141 | 140 P9 60 120 142 | 141 P10 60 100 143 | 142 P11 60 80 144 | 143 P12 60 60 145 | 144 P13 60 40 146 | 145 P14 60 20 147 | 146 dip16 P1 0 20 148 | 147 P2 0 40 149 | 148 P3 0 60 150 | 149 P4 0 80 151 | 150 P5 0 100 152 | 151 P6 0 120 153 | 152 P7 0 140 154 | 153 P8 0 160 155 | 154 P9 60 160 156 | 155 P10 60 140 157 | 156 P11 60 120 158 | 157 P12 60 100 159 | 158 P13 60 80 160 | 159 P14 60 60 161 | 160 P15 60 40 162 | 161 P16 60 20 163 | 162 ammeter P 20 0 164 | 163 N 20 60 165 | 164 inductor_coupling NC NC NC 166 | -------------------------------------------------------------------------------- /data/stripboard_specs_dip.tsv: -------------------------------------------------------------------------------- 1 | id model size pinout schematic 2 | 1 PC817 4x2 D+,D-,E,C dip 3 | 2 741 4x4 OFS1,IN-,IN+,V-,OFS2,OUT,V+,NC dip,dip8_opamp_single,opamp_single,opamp_single_offset 4 | 3 TL061 4x4 OFS1,IN-,IN+,V-,OFS2,OUT,V+,NC dip,dip8_opamp_single,opamp_single,opamp_single_offset 5 | 4 TL081 4x4 OFS1,IN-,IN+,V-,OFS2,OUT,V+,NC dip,dip8_opamp_single,opamp_single,opamp_single_offset 6 | 5 TL071 4x4 OFS1,IN-,IN+,V-,OFS2,OUT,V+,NC dip,dip8_opamp_single,opamp_single,opamp_single_offset 7 | 6 OP27 4x4 OFS1,IN-,IN+,V-,OFS2,OUT,V+,NC dip,dip8_opamp_single,opamp_single,opamp_single_offset 8 | 7 LM386 4x4 GAIN1,IN-,IN+,GND,OUT,VCC,BYPASS,GAIN2 dip 9 | 8 NE555 4x4 GND,TRIG,OUT,RESET,CTRL,THR,DIS,VCC dip 10 | 9 LM258 4x4 OUT1,IN1-,IN1+,V-,IN2+,IN2-,OUT2,V+ dip,dip8_opamp_dual,opamp_dual 11 | 10 LM358 4x4 OUT1,IN1-,IN1+,V-,IN2+,IN2-,OUT2,V+ dip,dip8_opamp_dual,opamp_dual 12 | 11 LM1458 4x4 OUT1,IN1-,IN1+,V-,IN2+,IN2-,OUT2,V+ dip,dip8_opamp_dual,opamp_dual 13 | 12 NE5532 4x4 OUT1,IN1-,IN1+,V-,IN2+,IN2-,OUT2,V+ dip,dip8_opamp_dual,opamp_dual 14 | 13 OP275 4x4 OUT1,IN1-,IN1+,V-,IN2+,IN2-,OUT2,V+ dip,dip8_opamp_dual,opamp_dual 15 | 14 NE5534 4x4 OFS1,IN-,IN+,V-,OFS2,OUT,V+,NC dip,dip8_opamp_single,opamp_single,opamp_single_offset 16 | 15 JRC4558 4x4 OUT1,IN1-,IN1+,V-,IN2+,IN2-,OUT2,V+ dip,dip8_opamp_dual,opamp_dual 17 | 16 JRC4580 4x4 OUT1,IN1-,IN1+,V-,IN2+,IN2-,OUT2,V+ dip,dip8_opamp_dual,opamp_dual 18 | 17 LM833 4x4 OUT1,IN1-,IN1+,V-,IN2+,IN2-,OUT2,V+ dip,dip8_opamp_dual,opamp_dual 19 | 18 LF353 4x4 OUT1,IN1-,IN1+,V-,IN2+,IN2-,OUT2,V+ dip,dip8_opamp_dual,opamp_dual 20 | 19 TL062 4x4 OUT1,IN1-,IN1+,V-,IN2+,IN2-,OUT2,V+ dip,dip8_opamp_dual,opamp_dual 21 | 20 TL072 4x4 OUT1,IN1-,IN1+,V-,IN2+,IN2-,OUT2,V+ dip,dip8_opamp_dual,opamp_dual 22 | 21 TL022 4x4 OUT1,IN1-,IN1+,V-,IN2+,IN2-,OUT2,V+ dip,dip8_opamp_dual,opamp_dual 23 | 22 TL082 4x4 OUT1,IN1-,IN1+,V-,IN2+,IN2-,OUT2,V+ dip,dip8_opamp_dual,opamp_dual 24 | 23 TL064 4x7 OUT1,IN1-,IN1+,V+,IN2+,IN2-,OUT2,OUT3,IN3-,IN3+,V-,IN4+,IN4-,OUT4 dip,dip8_opamp_quad,opamp_quad 25 | 24 TL074 4x7 OUT1,IN1-,IN1+,V+,IN2+,IN2-,OUT2,OUT3,IN3-,IN3+,V-,IN4+,IN4-,OUT4 dip,dip8_opamp_quad,opamp_quad 26 | 25 TL084 4x7 OUT1,IN1-,IN1+,V+,IN2+,IN2-,OUT2,OUT3,IN3-,IN3+,V-,IN4+,IN4-,OUT4 dip,dip8_opamp_quad,opamp_quad 27 | 26 LM324 4x7 OUT1,IN1-,IN1+,V+,IN2+,IN2-,OUT2,OUT3,IN3-,IN3+,V-,IN4+,IN4-,OUT4 dip,dip8_opamp_quad,opamp_quad 28 | 27 MAX1044 4x4 BOOST,CAP+,GND,CAP-,VOUT,LV,OSC,V+ dip 29 | 28 V3205D 4x7 GND,CP2,NC,NC,NC,OUT1,OUT2,VDD,CP1,NC,NC,NC,IN,VCC dip 30 | 29 PT2399 4x8 VCC,REF,AGND,DGND,CKL_O,VCO,CC1,CC0,OP1-OUT,OP1-IN,OP2-IN,OP2-OUT,LPF2-IN,LPF2-OUT,LPF1-OUT,LPF1-IN dip 31 | 30 CD4070 4x7 A1,B1,Q1,Q2,A2,B2,GND,A3,B3,Q3,Q4,A4,B4,VDD dip 32 | 31 CD4094 4x8 STROBE,DATA,CLK,Q1,Q2,Q3,Q4,VSS,QS1,QS2,Q8,Q7,Q6,Q5,OUT_ENABLE,VDD dip 33 | 32 V3102 4x4 GND,CLK1,VDD,CLK2,OX3,OX2,OX1,VGG dip 34 | 33 V571 4x8 RCAP1,RIN1,CIN1,GND,INV_IN1,R31,OUT1,THD1,THD2,OUT2,R32,INV_IN2,VCC,CIN2,RIN2,RCAP2 dip 35 | 34 CA3080 4x4 NC1,IN-,IN+,V-,OFS2,OUT,V+,NC2 dip 36 | 35 MN3007 4x4 GND,CP1,IN,VCC,VDD,CP2,OUT1,OUT2 dip 37 | 36 MN3101 4x4 GND,CLK1,VDD,CLK2,OX3,OX2,OX1,VGG dip 38 | 37 M5216L 4x4 OUT1,IN1-,IN1+,V-,IN2+,IN2-,OUT2,V+ dip,dip8_opamp_dual,opamp_dual 39 | 38 CD4049 4x8 VDD,Q1,A1,Q2,A2,Q3,A3,GND,A4,Q4,A5,Q5,NC,A6,Q6,NC dip 40 | 39 ICL7660S 4x4 BOOST,CAP+,GND,CAP-,VOUT,LV,OSC,V+ dip 41 | 40 LM308N 4x4 OFS1,IN-,IN+,V-,NC,OUT,V+,OFS2 dip,dip8_opamp_single,opamp_single,opamp_single_offset 42 | 41 LM13700 4x8 AB1,DB1,IN1+,IN1-,OUT1,V-,BI1,BO1,BO2,BI2,V+,OUT2,IN2-,IN2+,DB2,AB2 dip 43 | -------------------------------------------------------------------------------- /js/schematic/anchor.js: -------------------------------------------------------------------------------- 1 | // Anchor is point inside components image that is transformed the same way component is rotated/mirrored, it is used for optimal label placement 2 | "use strict"; 3 | // globals: document, window 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.anchor = function (aComponent, aAnchorX, aAnchorY) { 8 | // Calculate rotated/mirrored coordinates inside aComponent image 9 | var img = aComponent.image(), 10 | w = img.width, 11 | h = img.height; 12 | function mirror(x) { 13 | if (aComponent.mirror) { 14 | return w - x; 15 | } 16 | return x; 17 | } 18 | 19 | switch (aComponent.rotate) { 20 | case null: 21 | case undefined: 22 | case 0: 23 | return { 24 | x: mirror(aAnchorX), 25 | y: aAnchorY 26 | }; 27 | case 1: 28 | return { 29 | x: mirror(img.width - aAnchorY), 30 | y: aAnchorX 31 | }; 32 | case 2: 33 | return { 34 | x: mirror(w - aAnchorX), 35 | y: h - aAnchorY 36 | }; 37 | case 3: 38 | return { 39 | x: mirror(aAnchorY), 40 | y: h - aAnchorX 41 | }; 42 | } 43 | }; 44 | 45 | SC.anchorBox = function (aComponent, aX1, aY1, aX2, aY2) { 46 | // Calculate 2 rotated/mirrored coordinates inside aComponent image and return them sorted from left-top to right-bottom 47 | var a = SC.anchor(aComponent, aX1, aY1), 48 | b = SC.anchor(aComponent, aX2, aY2), 49 | r = { 50 | x: Math.min(a.x, b.x), 51 | y: Math.min(a.y, b.y), 52 | w: Math.abs(a.x - b.x), 53 | h: Math.abs(a.y - b.y) 54 | }; 55 | r.x2 = r.x + r.w; 56 | r.y2 = r.y + r.h; 57 | return r; 58 | }; 59 | 60 | SC.anchorBoxes = function (aComponent, aForceAnchors) { 61 | // Calculate 2 boxes and sort them left-top to right-bottom, so that name is 62 | // first and value second regardless of rotation and mirror 63 | var q = aForceAnchors || SC.specsAnchor[aComponent.type], a, b, c, img, w2, h2; 64 | if (!q) { 65 | return; 66 | } 67 | a = SC.anchorBox(aComponent, q.name.x1, q.name.y1, q.name.x2, q.name.y2); 68 | b = SC.anchorBox(aComponent, q.value.x1, q.value.y1, q.value.x2, q.value.y2); 69 | img = aComponent.image(); 70 | w2 = img.width / 2; 71 | h2 = img.height / 2; 72 | if (a.y > b.y) { 73 | c = b; 74 | b = a; 75 | a = c; 76 | } else { 77 | if (a.x > b.x) { 78 | c = b; 79 | b = a; 80 | a = c; 81 | } 82 | } 83 | // find textAlign and textBaseline 84 | a.textAlign = 'left'; 85 | a.textX = a.x; 86 | if (a.x < w2) { 87 | a.textAlign = 'right'; 88 | a.textX += a.w; 89 | } 90 | b.textAlign = 'left'; 91 | b.textX = b.x; 92 | if (b.x < w2) { 93 | b.textAlign = 'right'; 94 | b.textX += b.w; 95 | } 96 | a.textBaseline = 'top'; 97 | a.textY = a.y; 98 | if (a.y < h2) { 99 | a.textBaseline = 'alphabetic'; 100 | a.textY += a.h; 101 | } 102 | b.textBaseline = 'top'; 103 | b.textY = b.y; 104 | if (b.y < h2) { 105 | b.textBaseline = 'alphabetic'; 106 | b.textY += b.h; 107 | } 108 | return { 109 | a: a, 110 | b: b 111 | }; 112 | }; 113 | 114 | -------------------------------------------------------------------------------- /js/schematic/components.js: -------------------------------------------------------------------------------- 1 | // All components 2 | "use strict"; 3 | // globals: document, window 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.Components = function () { 8 | // Constructor 9 | this.item = []; 10 | }; 11 | 12 | SC.Components.prototype.toObject = function () { 13 | // Return exportable object 14 | var i, o = {}; 15 | o.item = []; 16 | for (i = 0; i < this.item.length; i++) { 17 | o.item.push(this.item[i].toObject()); 18 | } 19 | return o; 20 | }; 21 | 22 | SC.Components.prototype.fromObject = function (aObject) { 23 | // Set this from previously exported object 24 | var i, c; 25 | this.item = []; 26 | if (aObject.item) { 27 | for (i = 0; i < aObject.item.length; i++) { 28 | c = new SC.Component(); 29 | c.fromObject(aObject.item[i]); 30 | this.item.push(c); 31 | } 32 | } 33 | }; 34 | 35 | SC.Components.prototype.remove = function (aComponent) { 36 | // Remove component 37 | var i = this.item.indexOf(aComponent); 38 | if (i >= 0) { 39 | this.item.splice(i, 1); 40 | } 41 | SC.grid.updateNets(); 42 | }; 43 | 44 | SC.Components.prototype.render = function (aContext) { 45 | // Render all components 46 | var i; 47 | for (i = 0; i < this.item.length; i++) { 48 | this.item[i].render(aContext); 49 | } 50 | }; 51 | 52 | SC.Components.prototype.findByName = function (aName) { 53 | // Find component by name 54 | var i; 55 | for (i = 0; i < this.item.length; i++) { 56 | if (this.item[i].name === aName) { 57 | return this.item[i]; 58 | } 59 | } 60 | }; 61 | 62 | SC.Components.prototype.findByXY = function (aX, aY) { 63 | // Find item directly over given coords 64 | var i, c; 65 | for (i = 0; i < this.item.length; i++) { 66 | c = this.item[i]; 67 | if (aX >= c.x && aX < c.x + c.width && aY >= c.y && aY < c.y + c.height) { 68 | return c; 69 | } 70 | } 71 | }; 72 | 73 | SC.Components.prototype.findByPin = function (aX, aY) { 74 | // Find item with pin directly over given coords 75 | var i, p, xy; 76 | for (i = 0; i < this.item.length; i++) { 77 | for (p = 0; p < this.item[i].pin.length; p++) { 78 | xy = this.item[i].pinXY(p, false); 79 | if (xy.x === aX && xy.y === aY) { 80 | return { 81 | component: this.item[i], 82 | pin: this.item[i].pin[p].pin, 83 | index: this.item[i].pin[p].index 84 | }; 85 | } 86 | } 87 | } 88 | }; 89 | 90 | SC.Components.prototype.center = function () { 91 | // Find center point of all components (used in screenshots and after import) 92 | var i, c, minx = Number.MAX_VALUE, miny = Number.MAX_VALUE, maxx = -Number.MAX_VALUE, maxy = -Number.MAX_VALUE; 93 | for (i = 0; i < this.item.length; i++) { 94 | c = this.item[i].center(); 95 | minx = Math.min(minx, c.x); 96 | miny = Math.min(miny, c.y); 97 | maxx = Math.max(maxx, c.x); 98 | maxy = Math.max(maxy, c.y); 99 | } 100 | return { 101 | x: (maxx + minx) / 2, 102 | y: (maxy + miny) / 2 103 | }; 104 | }; 105 | 106 | SC.Components.prototype.updatePins = function () { 107 | // Update pins on all components (used only by netlist_random.js) 108 | var i; 109 | for (i = 0; i < this.item.length; i++) { 110 | this.item[i].updatePins(); 111 | } 112 | }; 113 | 114 | 115 | -------------------------------------------------------------------------------- /js/vec2/vec2.js: -------------------------------------------------------------------------------- 1 | // 2D vector math (all vector operations create new vector, for easier chaining, like Sylvester library) 2 | "use strict"; 3 | // globals: document, window 4 | 5 | function Vec2(x, y) { 6 | this.x = x || 0; 7 | this.y = y || 0; 8 | } 9 | 10 | function vec2(x, y) { 11 | return new Vec2(x, y); 12 | } 13 | 14 | Vec2.prototype.length = function () { 15 | return Math.sqrt(this.x * this.x + this.y * this.y); 16 | }; 17 | 18 | Vec2.prototype.distanceTo = function (v) { 19 | var dx = this.x - v.x, 20 | dy = this.y - v.y; 21 | return Math.sqrt(dx * dx + dy * dy); 22 | }; 23 | 24 | Vec2.prototype.clone = function () { 25 | return new Vec2(this.x, this.y); 26 | }; 27 | 28 | Vec2.prototype.add = function (vec2) { 29 | return new Vec2(this.x + vec2.x, this.y + vec2.y); 30 | }; 31 | 32 | Vec2.prototype.addXY = function (x, y) { 33 | return new Vec2(this.x + x, this.y + y); 34 | }; 35 | 36 | Vec2.prototype.sub = function (vec2) { 37 | return new Vec2(this.x - vec2.x, this.y - vec2.y); 38 | }; 39 | 40 | Vec2.prototype.mul = function (k) { 41 | return new Vec2(this.x * k, this.y * k); 42 | }; 43 | 44 | Vec2.prototype.mulX = function (k) { 45 | return new Vec2(this.x * k, this.y); 46 | }; 47 | 48 | Vec2.prototype.mulY = function (k) { 49 | return new Vec2(this.x, this.y * k); 50 | }; 51 | 52 | Vec2.prototype.mulXY = function (kx, ky) { 53 | return new Vec2(this.x * kx, this.y * ky); 54 | }; 55 | 56 | Vec2.prototype.left = function () { 57 | return new Vec2(-this.y, this.x); 58 | }; 59 | 60 | Vec2.prototype.right = function () { 61 | return new Vec2(this.y, -this.x); 62 | }; 63 | 64 | Vec2.prototype.neg = function () { 65 | return new Vec2(-this.x, -this.y); 66 | }; 67 | 68 | Vec2.prototype.unit = function () { 69 | var d = this.length(); 70 | return new Vec2(this.x / d, this.y / d); 71 | }; 72 | 73 | Vec2.prototype.angle = function (aPositive) { 74 | var a = Math.atan2(this.y, this.x); 75 | if (aPositive && (a < 0)) { 76 | a += 2 * Math.PI; 77 | return a; 78 | } 79 | return a; 80 | }; 81 | 82 | Vec2.prototype.angleTo = function (v) { 83 | var a = this.angle(), 84 | b = v.angle(), 85 | r = a - b; 86 | if (r < -Math.PI) { 87 | return r + 2 * Math.PI; 88 | } 89 | if (r > Math.PI) { 90 | return r - 2 * Math.PI; 91 | } 92 | return r; 93 | }; 94 | 95 | Vec2.prototype.fromAngle = function (aAngle) { 96 | return new Vec2(Math.cos(aAngle), Math.sin(aAngle)); 97 | }; 98 | 99 | Vec2.prototype.fromObject = function (aObject) { 100 | return new Vec2(aObject.x, aObject.y); 101 | }; 102 | 103 | Vec2.prototype.rotate = function (aAngle) { 104 | var a = this.angle() - aAngle, 105 | l = this.length(); 106 | return new Vec2(l * Math.cos(a), l * Math.sin(a)); 107 | }; 108 | 109 | Vec2.prototype.dot = function (v) { 110 | return this.x * v.x + this.y * v.y; 111 | }; 112 | 113 | Vec2.prototype.cross = function (v) { 114 | return this.x * v.y - v.x * this.y; 115 | }; 116 | 117 | Vec2.prototype.round = function () { 118 | return new Vec2(Math.round(this.x), Math.round(this.y)); 119 | }; 120 | 121 | Vec2.prototype.ceil = function () { 122 | return new Vec2(Math.ceil(this.x), Math.ceil(this.y)); 123 | }; 124 | 125 | Vec2.prototype.floor = function () { 126 | return new Vec2(Math.floor(this.x), Math.floor(this.y)); 127 | }; 128 | 129 | Vec2.prototype.toString = function (aName) { 130 | var a = this.angle(); 131 | a = 180 * a / Math.PI; 132 | return (aName ? aName + ' ' : '') + this.x.toFixed(2).replace('.00', '') + ' ' + this.y.toFixed(2).replace('.00', '') + ' angle=' + a.toFixed(1).replace('.0', '') + '°'; 133 | }; 134 | 135 | -------------------------------------------------------------------------------- /js/stripboard/component_connector.js: -------------------------------------------------------------------------------- 1 | // Connector comming out of edge of the stripboard, like a link but comes OUT of board and has a name 2 | "use strict"; 3 | // globals: document, window, CA 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.connectorNextName = ''; 8 | 9 | SC.Connector = function (aName, aX1, aY1, aX2, aY2) { 10 | this.is_connector = true; 11 | this.type = 'connector'; 12 | this.name = SC.connectorNextName || aName; 13 | SC.connectorNextName = ''; 14 | this.x1 = aX1 || 0; 15 | this.y1 = aY1 || 0; 16 | this.x2 = aX2 || 0; 17 | this.y2 = aY2 || 0; 18 | }; 19 | 20 | SC.factory.connector = SC.Connector; 21 | SC.namingPrefix.connector = 'X'; 22 | 23 | SC.Connector.prototype.toObject = function () { 24 | // Return exportable object 25 | return { 26 | type: this.type, 27 | name: this.name, 28 | x1: this.x1, 29 | y1: this.y1, 30 | x2: this.x2, 31 | y2: this.y2 32 | }; 33 | }; 34 | 35 | SC.Connector.prototype.fromObject = function (aObject) { 36 | // Set this from previously exported object 37 | this.name = aObject.name; 38 | this.x1 = aObject.x1; 39 | this.y1 = aObject.y1; 40 | this.x2 = aObject.x2; 41 | this.y2 = aObject.y2; 42 | }; 43 | 44 | SC.Connector.prototype.toNetlist = function () { 45 | // Return netlist object 46 | if (this.name === 'Ground') { 47 | return {type: 'ground', name: "Ground", nets: this.nets()}; 48 | } 49 | return { 50 | type: this.type, 51 | name: this.name, 52 | nets: this.nets() 53 | }; 54 | }; 55 | 56 | SC.Connector.prototype.nets = function () { 57 | // Return nets connected to pins 58 | this.x1 = this.x1 || 0; 59 | this.y1 = this.y1 || 0; 60 | return [ 61 | SC.grid.net(this.x1, this.y1) 62 | ]; 63 | }; 64 | 65 | SC.Connector.prototype.hasPin = function (aX, aY) { 66 | // Return true if given coords are at any of the pins, only start of connector is considered "real" 67 | return (this.x1 === aX) && (this.y1 === aY); 68 | }; 69 | 70 | SC.Connector.prototype.fixPinOrder = function (aDialog) { 71 | // Connector must start on board and end outside board, if it is other way around fix it 72 | return SC.components.fixPinOrder(this, aDialog); 73 | }; 74 | 75 | SC.Connector.prototype.distanceTo = function (aX, aY) { 76 | // Return distance to this connector's line 77 | return CA.distancePointLineSegment(aX, aY, this.x1, this.y1, this.x2, this.y2); 78 | }; 79 | 80 | SC.Connector.prototype.render = function (aContext) { 81 | // Render connector 82 | var a = SC.grid.gridToScreen(this.x1, this.y1, true), 83 | b = SC.grid.gridToScreen(this.x2, this.y2, true), 84 | line_width = 4 * SC.v.zoom, 85 | dx; 86 | // name 87 | aContext.globalAlpha = 1; 88 | aContext.font = 'bold 12px sans-serif'; 89 | aContext.fillStyle = 'black'; 90 | aContext.strokeStyle = 'white'; 91 | aContext.lineWidth = 1; 92 | aContext.textBaseline = 'middle'; 93 | if (this.x1 === 0) { 94 | aContext.textAlign = 'right'; 95 | dx = -line_width; 96 | } else { 97 | aContext.textAlign = 'left'; 98 | dx = line_width; 99 | } 100 | aContext.strokeText(this.name, b.x + dx, b.y); 101 | aContext.fillText(this.name, b.x + dx, b.y); 102 | // line 103 | aContext.globalAlpha = 0.5; 104 | aContext.lineCap = 'round'; 105 | aContext.lineWidth = line_width; 106 | aContext.strokeStyle = 'black'; 107 | aContext.beginPath(); 108 | aContext.moveTo(a.x, a.y); 109 | aContext.lineTo(b.x, b.y); 110 | aContext.stroke(); 111 | aContext.globalAlpha = 1; 112 | }; 113 | 114 | SC.Connector.prototype.propertiesDialog = function (aCallback) { 115 | // Show properties dialog 116 | // Certain names don't show dialog 117 | if (this.name === 'Ground' || this.name === 'Input' || this.name === 'Output') { 118 | aCallback('Add'); 119 | return; 120 | } 121 | return SC.propertiesDialog(this, '', aCallback); 122 | }; 123 | 124 | -------------------------------------------------------------------------------- /js/stripboard/component_connector_battery.js: -------------------------------------------------------------------------------- 1 | // Connector for battery (has extra field for voltage) 2 | "use strict"; 3 | // globals: document, window, CA 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.ConnectorBattery = function (aName, aX1, aY1, aX2, aY2) { 8 | this.is_connector = true; 9 | this.type = 'connector_battery'; 10 | this.name = aName; 11 | this.x1 = aX1 || 0; 12 | this.y1 = aY1 || 0; 13 | this.x2 = aX2 || 0; 14 | this.y2 = aY2 || 0; 15 | this.value = ''; 16 | }; 17 | 18 | SC.factory.connector_battery = SC.ConnectorBattery; 19 | SC.namingPrefix.connector_battery = 'U'; 20 | 21 | SC.ConnectorBattery.prototype.toObject = function () { 22 | // Return exportable object 23 | return { 24 | type: this.type, 25 | name: this.name, 26 | x1: this.x1, 27 | y1: this.y1, 28 | x2: this.x2, 29 | y2: this.y2, 30 | value: this.value 31 | }; 32 | }; 33 | 34 | SC.ConnectorBattery.prototype.fromObject = function (aObject) { 35 | // Set this from previously exported object 36 | this.name = aObject.name; 37 | this.x1 = aObject.x1; 38 | this.y1 = aObject.y1; 39 | this.x2 = aObject.x2; 40 | this.y2 = aObject.y2; 41 | this.value = aObject.value; 42 | }; 43 | 44 | SC.ConnectorBattery.prototype.toNetlist = function () { 45 | // Return netlist object (both are |9V| but -9 has flipped nets) 46 | return { 47 | type: 'dc_voltage_supply', 48 | name: this.name, 49 | nets: parseFloat(this.value) >= 0 ? [this.nets()[0], SC.components.groundNet()] : [SC.components.groundNet(), this.nets()[0]], 50 | value: Math.abs(parseFloat(this.value)) 51 | }; 52 | }; 53 | 54 | SC.ConnectorBattery.prototype.nets = function () { 55 | // Return nets connected to pins 56 | this.x1 = this.x1 || 0; 57 | this.y1 = this.y1 || 0; 58 | return [ 59 | SC.grid.net(this.x1, this.y1) 60 | ]; 61 | }; 62 | 63 | SC.ConnectorBattery.prototype.hasPin = function (aX, aY) { 64 | // Return true if given coords are at any of the pins 65 | return (this.x1 === aX) && (this.y1 === aY); 66 | }; 67 | 68 | SC.ConnectorBattery.prototype.fixPinOrder = function (aDialog) { 69 | // Connector must start on board and end outside board, if it is other way around fix it 70 | return SC.components.fixPinOrder(this, aDialog); 71 | }; 72 | 73 | SC.ConnectorBattery.prototype.distanceTo = function (aX, aY) { 74 | // Return distance to this connector line 75 | return CA.distancePointLineSegment(aX, aY, this.x1, this.y1, this.x2, this.y2); 76 | }; 77 | 78 | SC.ConnectorBattery.prototype.render = function (aContext) { 79 | // Render connector 80 | var a = SC.grid.gridToScreen(this.x1, this.y1, true), 81 | b = SC.grid.gridToScreen(this.x2, this.y2, true), 82 | line_width = 4 * SC.v.zoom, 83 | dx; 84 | // name + value 85 | aContext.globalAlpha = 1; 86 | aContext.font = 'bold 12px sans-serif'; 87 | aContext.fillStyle = 'black'; 88 | aContext.strokeStyle = 'white'; 89 | aContext.lineWidth = 1; 90 | aContext.textBaseline = 'middle'; 91 | if (this.x1 === 0) { 92 | aContext.textAlign = 'right'; 93 | dx = -line_width; 94 | } else { 95 | aContext.textAlign = 'left'; 96 | dx = line_width; 97 | } 98 | aContext.strokeText(this.name + ' ' + (this.value ? this.value + 'V' : ''), b.x + dx, b.y); 99 | aContext.fillText(this.name + ' ' + (this.value ? this.value + 'V' : ''), b.x + dx, b.y); 100 | // line 101 | aContext.globalAlpha = 0.5; 102 | aContext.lineCap = 'round'; 103 | aContext.lineWidth = line_width; 104 | aContext.strokeStyle = 'black'; 105 | aContext.beginPath(); 106 | aContext.moveTo(a.x, a.y); 107 | aContext.lineTo(b.x, b.y); 108 | aContext.stroke(); 109 | aContext.globalAlpha = 1; 110 | }; 111 | 112 | SC.ConnectorBattery.prototype.propertiesDialog = function (aCallback) { 113 | // Show properties dialog 114 | var d = SC.propertiesDialog(this, 'V', aCallback); 115 | d.value.label.textContent = 'Voltage'; 116 | CA.labelInputLabelsSameWidth(); 117 | return d; 118 | }; 119 | 120 | -------------------------------------------------------------------------------- /js/schematic/nets.js: -------------------------------------------------------------------------------- 1 | // All nets 2 | "use strict"; 3 | // globals: document, window, CA 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.Nets = function () { 8 | // Constructor 9 | this.item = []; 10 | }; 11 | 12 | SC.Nets.prototype.toObject = function () { 13 | // Return exportable object 14 | var i, r = {item: []}; 15 | for (i = 0; i < this.item.length; i++) { 16 | r.item.push(this.item[i].toObject()); 17 | } 18 | return r; 19 | }; 20 | 21 | SC.Nets.prototype.fromObject = function (aObject) { 22 | // Set this from previously exported object 23 | var i, o; 24 | this.item = []; 25 | for (i = 0; i < aObject.item.length; i++) { 26 | o = new SC.Net(); 27 | o.fromObject(aObject.item[i]); 28 | this.item.push(o); 29 | } 30 | }; 31 | 32 | SC.Nets.prototype.render = function (aContext) { 33 | // Render all nets 34 | var i, j, k, l, p, a, b, s, t, z, size = 8 * SC.v.zoom / SC.gridSize; 35 | // nets 36 | this.total_length = 0; 37 | for (i = 0; i < this.item.length; i++) { 38 | this.total_length += this.item[i].render(aContext); 39 | } 40 | // show red squares where different nets cross 41 | this.intersects = 0; 42 | if (SC.show.net_crossings) { 43 | if (aContext) { 44 | aContext.strokeStyle = 'red'; 45 | aContext.lineWidth = 1; 46 | } 47 | // all nets 48 | for (i = 0; i < this.item.length - 1; i++) { 49 | a = this.item[i]; 50 | // against all other nets 51 | for (j = i + 1; j < this.item.length; j++) { 52 | b = this.item[j]; 53 | // find intersections of all segments of both nets 54 | for (k = 0; k < a.kruskal.length; k++) { 55 | for (l = 0; l < b.kruskal.length; l++) { 56 | s = a.kruskal[k]; 57 | t = b.kruskal[l]; 58 | p = CA.lineIntersection(s.x1, s.y1, s.x2, s.y2, t.x1, t.y1, t.x2, t.y2); 59 | //console.log(s.x1, s.y1, s.x2, s.y2, t.x1, t.y1, t.x2, t.y2) 60 | //console.log(p); 61 | if (p && p.intersects) { 62 | if (aContext) { 63 | // draw red box 64 | z = SC.v.canvasToScreen(p.x, p.y); 65 | aContext.strokeRect(z.x - size / 2, z.y - size / 2, size, size); 66 | // collinear may intersects in 2 points 67 | if (p.hasOwnProperty('x2')) { 68 | z = SC.v.canvasToScreen(p.x2, p.y2); 69 | aContext.strokeRect(z.x - size / 2, z.y - size / 2, size, size); 70 | this.intersects++; 71 | } 72 | } 73 | this.intersects++; 74 | } 75 | } 76 | } 77 | } 78 | } 79 | } 80 | }; 81 | 82 | SC.Nets.prototype.nearest = function (aX, aY) { 83 | // Find nearest net 84 | var d, m = Number.MAX_VALUE, i, best; 85 | for (i = 0; i < this.item.length; i++) { 86 | d = this.item[i].distanceTo(aX, aY); 87 | if (d < m) { 88 | m = d; 89 | best = this.item[i]; 90 | } 91 | } 92 | return { 93 | net: best, 94 | distance: m 95 | }; 96 | }; 97 | 98 | SC.Nets.prototype.addComponentPin = function (aNet, aComponent, aPin) { 99 | // Add component's pin to a net 100 | aComponent.pinNet[aPin] = aNet; 101 | // not connected pins 102 | if (!aNet) { 103 | return; 104 | } 105 | // find net with this id 106 | var i, n; 107 | for (i = 0; i < this.item.length; i++) { 108 | if (this.item[i].id === aNet) { 109 | n = this.item[i]; 110 | } 111 | } 112 | // if first pin in a net make a new net? 113 | if (!n) { 114 | n = new SC.Net(aNet); 115 | this.item.push(n); 116 | } 117 | // add pin to the net's pins 118 | n.pins.push({component: aComponent, pin: aPin}); 119 | }; 120 | 121 | -------------------------------------------------------------------------------- /js/schematic/rotate_and_mirror.js: -------------------------------------------------------------------------------- 1 | // Cache rotated and mirrored images of components (8 images for each component, 4 rotation * 2 mirror) 2 | "use strict"; 3 | // globals: document, window, CA 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.rotateAndMirror = function (aRotate, aMirror, aX, aY, aWidth, aHeight) { 8 | // Calculate rotate and mirror transformation for single point in image 9 | var x, y, l, angle, angle2, x2, y2, q; 10 | q = 0; 11 | if (aRotate === 3 && aMirror) { 12 | q = aWidth - aHeight; 13 | } 14 | if (aRotate === 1) { 15 | aHeight = aWidth; 16 | } else if (aRotate === 3) { 17 | aWidth = aHeight; 18 | } 19 | x = aX - aWidth / 2; 20 | y = aY - aHeight / 2; 21 | l = Math.sqrt(x * x + y * y); 22 | angle = Math.atan2(y, x); 23 | angle2 = angle + Math.PI * aRotate / 2; 24 | x2 = l * Math.cos(angle2); 25 | y2 = l * Math.sin(angle2); 26 | //console.log({x,y,angle,angle2,l,x2,y2}); 27 | return { 28 | x: Math.round((aWidth / 2 + (aMirror ? -1 : 1) * x2 + q) / SC.gridSize), 29 | y: Math.round((aHeight / 2 + y2) / SC.gridSize) 30 | }; 31 | }; 32 | 33 | SC.loadImagesAndTransform = function (aPins, aCallback) { 34 | // Load all images specified in aPins and make all 8 rotate/mirror combinations 35 | var src = Object.keys(aPins).map(function (a) { return 'image/schematic/' + a + '.png'; }); 36 | 37 | CA.images(src, function (aImages) { 38 | var k, type; 39 | 40 | function one(aImage, aMirror, aRotate) { 41 | // Load one image and make 8 canvases 42 | var w, h, can, ctx, x, y; 43 | w = aImage.width; 44 | h = aImage.height; 45 | if (aRotate % 2 === 1) { 46 | w = aImage.height; 47 | h = aImage.width; 48 | } 49 | can = document.createElement('canvas'); 50 | can.width = w; 51 | can.height = h; 52 | ctx = can.getContext('2d'); 53 | ctx.fillStyle = 'silver'; 54 | if (SC.grayComponents) { 55 | ctx.fillRect(0, 0, w, h); 56 | } 57 | x = 0; 58 | y = 0; 59 | if (aMirror) { 60 | ctx.scale(-1, 1); 61 | } 62 | switch (aRotate) { 63 | case 0: 64 | ctx.rotate(aRotate * Math.PI / 2); 65 | if (aMirror) { 66 | x = -w; 67 | y = 0; 68 | } 69 | ctx.drawImage(aImage, x, y); 70 | break; 71 | case 1: 72 | ctx.rotate(aRotate * Math.PI / 2); 73 | if (aMirror) { 74 | x = 0; 75 | y = w; 76 | } 77 | ctx.drawImage(aImage, x, -w + y); 78 | break; 79 | case 2: 80 | ctx.rotate(aRotate * Math.PI / 2); 81 | if (aMirror) { 82 | x = w; 83 | y = 0; 84 | } 85 | ctx.drawImage(aImage, -w + x, -h + y); 86 | break; 87 | case 3: 88 | ctx.rotate(aRotate * Math.PI / 2); 89 | if (aMirror) { 90 | x = 0; 91 | y = -w; 92 | } 93 | ctx.drawImage(aImage, -h + x, y); 94 | break; 95 | } 96 | return can; 97 | } 98 | 99 | for (k in aImages) { 100 | if (aImages.hasOwnProperty(k)) { 101 | type = k.replace('image/schematic/', '').replace('.png', ''); 102 | SC.image[type] = { 103 | 0: one(aImages[k], false, 0), 104 | 1: one(aImages[k], false, 1), 105 | 2: one(aImages[k], false, 2), 106 | 3: one(aImages[k], false, 3), 107 | 10: one(aImages[k], true, 0), 108 | 11: one(aImages[k], true, 1), 109 | 12: one(aImages[k], true, 2), 110 | 13: one(aImages[k], true, 3) 111 | }; 112 | } 113 | } 114 | 115 | aCallback(); 116 | }); 117 | }; 118 | -------------------------------------------------------------------------------- /js/stripboard/component_connector_switch_spst.js: -------------------------------------------------------------------------------- 1 | // SPST Switch connector 2 | "use strict"; 3 | // globals: document, window, CA 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.ConnectorSwitchSPST = function (aName, aX1, aY1, aX2, aY2) { 8 | this.is_connector = true; 9 | this.type = 'connector_switch_spst'; 10 | this.name = aName; 11 | this.x1 = aX1 || 0; 12 | this.y1 = aY1 || 0; 13 | this.x2 = aX2 || 0; 14 | this.y2 = aY2 || 0; 15 | this.pin = 0; 16 | }; 17 | 18 | SC.factory.connector_switch_spst = SC.ConnectorSwitchSPST; 19 | SC.namingPrefix.connector_switch_spst = ''; 20 | 21 | SC.ConnectorSwitchSPST.PIN_NAMES = { 22 | 0: '', 23 | 1: 'Input', 24 | 2: 'Output' 25 | }; 26 | 27 | SC.ConnectorSwitchSPST.prototype.toObject = function () { 28 | // Return exportable object 29 | return { 30 | type: this.type, 31 | name: this.name, 32 | x1: this.x1, 33 | y1: this.y1, 34 | x2: this.x2, 35 | y2: this.y2, 36 | pin: this.pin 37 | }; 38 | }; 39 | 40 | SC.ConnectorSwitchSPST.prototype.fromObject = function (aObject) { 41 | // Set this from previously exported object 42 | this.name = aObject.name; 43 | this.x1 = aObject.x1; 44 | this.y1 = aObject.y1; 45 | this.x2 = aObject.x2; 46 | this.y2 = aObject.y2; 47 | this.pin = aObject.pin; 48 | }; 49 | 50 | SC.ConnectorSwitchSPST.prototype.toNetlist = function () { 51 | // Netlist of external SwitchSPST are made by merging few ConnectorSwitchSPST in netlist.js 52 | return; 53 | }; 54 | 55 | SC.ConnectorSwitchSPST.prototype.nets = function () { 56 | // Return nets connected to pins 57 | return [ 58 | SC.grid.net(this.x1, this.y1) 59 | ]; 60 | }; 61 | 62 | SC.ConnectorSwitchSPST.prototype.hasPin = function (aX, aY) { 63 | // Return true if given coords are at any of the pins, only start of connector is considered "real" 64 | return (this.x1 === aX) && (this.y1 === aY); 65 | }; 66 | 67 | SC.ConnectorSwitchSPST.prototype.fixPinOrder = function (aDialog) { 68 | // Connector must start on board and end outside board, if it is other way around fix it 69 | return SC.components.fixPinOrder(this, aDialog); 70 | }; 71 | 72 | SC.ConnectorSwitchSPST.prototype.distanceTo = function (aX, aY) { 73 | // Return distance to given point on grid 74 | return CA.distancePointLineSegment(aX, aY, this.x1, this.y1, this.x2, this.y2); 75 | }; 76 | 77 | SC.ConnectorSwitchSPST.prototype.render = function (aContext) { 78 | // Render connector 79 | var a = SC.grid.gridToScreen(this.x1, this.y1, true), 80 | b = SC.grid.gridToScreen(this.x2, this.y2, true), 81 | line_width = 4 * SC.v.zoom; 82 | // name and pin 83 | aContext.globalAlpha = 1; 84 | aContext.font = 'bold 12px sans-serif'; 85 | aContext.fillStyle = 'black'; 86 | aContext.lineWidth = 1; 87 | aContext.strokeStyle = 'white'; 88 | aContext.textBaseline = 'middle'; 89 | if (this.x1 === 0) { 90 | aContext.textAlign = 'right'; 91 | aContext.strokeText(this.name + ' ' + SC.ConnectorSwitchSPST.PIN_NAMES[this.pin], b.x - line_width - (2 * line_width - 4), b.y); 92 | aContext.fillText(this.name + ' ' + SC.ConnectorSwitchSPST.PIN_NAMES[this.pin], b.x - line_width - (2 * line_width - 4), b.y); 93 | } else { 94 | aContext.textAlign = 'left'; 95 | aContext.strokeText(this.name + ' ' + SC.ConnectorSwitchSPST.PIN_NAMES[this.pin], b.x + line_width + (2 * line_width + 4), b.y); 96 | aContext.fillText(this.name + ' ' + SC.ConnectorSwitchSPST.PIN_NAMES[this.pin], b.x + line_width + (2 * line_width + 4), b.y); 97 | } 98 | // line 99 | aContext.globalAlpha = 0.5; 100 | aContext.lineCap = 'round'; 101 | aContext.lineWidth = line_width; 102 | aContext.strokeStyle = 'black'; 103 | aContext.beginPath(); 104 | aContext.moveTo(a.x, a.y); 105 | aContext.lineTo(b.x, b.y); 106 | aContext.stroke(); 107 | aContext.globalAlpha = 1; 108 | }; 109 | 110 | SC.ConnectorSwitchSPST.prototype.propertiesDialog = function (aCallback) { 111 | // Show properties dialog 112 | var t = this, d = SC.propertiesDialog(this, '', function (aButton) { 113 | aCallback(aButton); 114 | if (aButton === 'Save') { 115 | t.pin = d.pin.input.value; 116 | } 117 | SC.render(); 118 | }); 119 | d.dlg.h1.textContent = 'Connector for SPST switch'; 120 | 121 | d.pin = CA.labelInput(d.dlg.content, 'Pin', CA.select(SC.ConnectorSwitchSPST.PIN_NAMES)); 122 | d.pin.input.value = t.pin; 123 | 124 | d.img = document.createElement('img'); 125 | d.img.src = 'image/stripboard/spst.png'; 126 | d.img.style.marginLeft = CA.labelInputLabels[0].clientWidth + 'px'; 127 | d.dlg.content.appendChild(d.img); 128 | 129 | return d; 130 | }; 131 | 132 | -------------------------------------------------------------------------------- /js/stripboard/component_connector_switch_spdt.js: -------------------------------------------------------------------------------- 1 | // SPDT Switch connector 2 | "use strict"; 3 | // globals: document, window, CA 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.ConnectorSwitchSPDT = function (aName, aX1, aY1, aX2, aY2) { 8 | this.is_connector = true; 9 | this.type = 'connector_switch_spdt'; 10 | this.name = aName; 11 | this.x1 = aX1 || 0; 12 | this.y1 = aY1 || 0; 13 | this.x2 = aX2 || 0; 14 | this.y2 = aY2 || 0; 15 | this.pin = 0; 16 | }; 17 | 18 | SC.factory.connector_switch_spdt = SC.ConnectorSwitchSPDT; 19 | SC.namingPrefix.connector_switch_spdt = ''; 20 | 21 | SC.ConnectorSwitchSPDT.PIN_NAMES = { 22 | 0: '', 23 | 1: 'Input', 24 | 2: 'Output A', 25 | 3: 'Output B' 26 | }; 27 | 28 | SC.ConnectorSwitchSPDT.prototype.toObject = function () { 29 | // Return exportable object 30 | return { 31 | type: this.type, 32 | name: this.name, 33 | x1: this.x1, 34 | y1: this.y1, 35 | x2: this.x2, 36 | y2: this.y2, 37 | pin: this.pin 38 | }; 39 | }; 40 | 41 | SC.ConnectorSwitchSPDT.prototype.fromObject = function (aObject) { 42 | // Set this from previously exported object 43 | this.name = aObject.name; 44 | this.x1 = aObject.x1; 45 | this.y1 = aObject.y1; 46 | this.x2 = aObject.x2; 47 | this.y2 = aObject.y2; 48 | this.pin = aObject.pin; 49 | }; 50 | 51 | SC.ConnectorSwitchSPDT.prototype.toNetlist = function () { 52 | // Netlist of external SwitchSPDT are made by merging few ConnectorSwitchSPDT in netlist.js 53 | return; 54 | }; 55 | 56 | SC.ConnectorSwitchSPDT.prototype.nets = function () { 57 | // Return nets connected to pins 58 | return [ 59 | SC.grid.net(this.x1, this.y1) 60 | ]; 61 | }; 62 | 63 | SC.ConnectorSwitchSPDT.prototype.hasPin = function (aX, aY) { 64 | // Return true if given coords are at any of the pins, only start of connector is considered "real" 65 | return (this.x1 === aX) && (this.y1 === aY); 66 | }; 67 | 68 | SC.ConnectorSwitchSPDT.prototype.fixPinOrder = function (aDialog) { 69 | // Connector must start on board and end outside board, if it is other way around fix it 70 | return SC.components.fixPinOrder(this, aDialog); 71 | }; 72 | 73 | SC.ConnectorSwitchSPDT.prototype.distanceTo = function (aX, aY) { 74 | // Return distance to given point on grid 75 | return CA.distancePointLineSegment(aX, aY, this.x1, this.y1, this.x2, this.y2); 76 | }; 77 | 78 | SC.ConnectorSwitchSPDT.prototype.render = function (aContext) { 79 | // Render connector 80 | var a = SC.grid.gridToScreen(this.x1, this.y1, true), 81 | b = SC.grid.gridToScreen(this.x2, this.y2, true), 82 | line_width = 4 * SC.v.zoom; 83 | // name 84 | aContext.globalAlpha = 1; 85 | aContext.font = 'bold 12px sans-serif'; 86 | aContext.fillStyle = 'black'; 87 | aContext.lineWidth = 1; 88 | aContext.strokeStyle = 'white'; 89 | aContext.textBaseline = 'middle'; 90 | if (this.x1 === 0) { 91 | aContext.textAlign = 'right'; 92 | aContext.strokeText(this.name + ' ' + SC.ConnectorSwitchSPDT.PIN_NAMES[this.pin], b.x - line_width - (2 * line_width - 4), b.y); 93 | aContext.fillText(this.name + ' ' + SC.ConnectorSwitchSPDT.PIN_NAMES[this.pin], b.x - line_width - (2 * line_width - 4), b.y); 94 | } else { 95 | aContext.textAlign = 'left'; 96 | aContext.strokeText(this.name + ' ' + SC.ConnectorSwitchSPDT.PIN_NAMES[this.pin], b.x + line_width + (2 * line_width + 4), b.y); 97 | aContext.fillText(this.name + ' ' + SC.ConnectorSwitchSPDT.PIN_NAMES[this.pin], b.x + line_width + (2 * line_width + 4), b.y); 98 | } 99 | // line 100 | aContext.globalAlpha = 0.5; 101 | aContext.lineCap = 'round'; 102 | aContext.lineWidth = line_width; 103 | aContext.strokeStyle = 'black'; 104 | aContext.beginPath(); 105 | aContext.moveTo(a.x, a.y); 106 | aContext.lineTo(b.x, b.y); 107 | aContext.stroke(); 108 | aContext.globalAlpha = 1; 109 | }; 110 | 111 | SC.ConnectorSwitchSPDT.prototype.propertiesDialog = function (aCallback) { 112 | // Show properties dialog 113 | var t = this, d = SC.propertiesDialog(this, '', function (aButton) { 114 | aCallback(aButton); 115 | if (aButton === 'Save') { 116 | t.pin = d.pin.input.value; 117 | } 118 | SC.render(); 119 | }); 120 | d.dlg.h1.textContent = 'Connector for SPDT switch'; 121 | 122 | d.pin = CA.labelInput(d.dlg.content, 'Pin', CA.select(SC.ConnectorSwitchSPDT.PIN_NAMES)); 123 | d.pin.input.value = t.pin; 124 | 125 | d.img = document.createElement('img'); 126 | d.img.src = 'image/stripboard/spdt.png'; 127 | d.img.style.marginLeft = CA.labelInputLabels[0].clientWidth + 'px'; 128 | d.dlg.content.appendChild(d.img); 129 | 130 | return d; 131 | }; 132 | 133 | -------------------------------------------------------------------------------- /js/schematic/guides.js: -------------------------------------------------------------------------------- 1 | // All Guides (guide points where net should go) 2 | "use strict"; 3 | // globals: document, window, CA 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.Guides = function () { 8 | // Constructor 9 | this.item = []; 10 | }; 11 | 12 | SC.Guides.prototype.toObject = function () { 13 | // Return exportable object 14 | var i, o = {}; 15 | o.item = []; 16 | for (i = 0; i < this.item.length; i++) { 17 | o.item.push(this.item[i].toObject()); 18 | } 19 | return o; 20 | }; 21 | 22 | SC.Guides.prototype.fromObject = function (aObject) { 23 | // Set this from previously exported object 24 | var i, c; 25 | this.item = []; 26 | if (aObject.item) { 27 | for (i = 0; i < aObject.item.length; i++) { 28 | c = new SC.Guide(); 29 | c.fromObject(aObject.item[i]); 30 | this.item.push(c); 31 | } 32 | } 33 | }; 34 | 35 | SC.Guides.prototype.add = function (aX, aY) { 36 | // Add Guide at given coordinates (to a nearest net) 37 | var i, v, n; 38 | v = new SC.Guide(0, Math.round(aX), Math.round(aY)), 39 | n = SC.nets.nearest(aX, aY); 40 | v.net = n.net.id; 41 | // don't add same Guide twice 42 | for (i = 0; i < this.item.length; i++) { 43 | if (this.item[i].x === v.x && this.item[i].y === v.y) { // && this.item[i].net === v.net 44 | // but still return Guide in case I need net id 45 | return v; 46 | } 47 | } 48 | this.item.push(v); 49 | return v; 50 | }; 51 | 52 | SC.Guides.prototype.remove = function (aGuide) { 53 | // Remove Guide 54 | var i = this.item.indexOf(aGuide); 55 | if (i >= 0) { 56 | this.item.splice(i, 1); 57 | } 58 | }; 59 | 60 | SC.Guides.prototype.render = function (aContext) { 61 | // Render all Guides 62 | var i; 63 | for (i = 0; i < this.item.length; i++) { 64 | this.item[i].render(aContext); 65 | } 66 | }; 67 | 68 | SC.Guides.prototype.cacheXY = function () { 69 | // Create [x y] cache of Guides, used for adding guide wire efficiently 70 | var i, o = {}; 71 | for (i = 0; i < this.item.length; i++) { 72 | o[this.item[i].x + ' ' + this.item[i].y] = this.item[i]; 73 | } 74 | return o; 75 | }; 76 | 77 | SC.Guides.prototype.findByXY = function (aX, aY) { 78 | // Find item directly over given integer coords 79 | var i, c; 80 | for (i = 0; i < this.item.length; i++) { 81 | c = this.item[i]; 82 | if (aX === c.x && aY === c.y) { 83 | return c; 84 | } 85 | } 86 | }; 87 | 88 | SC.Guides.prototype.findNearest = function (aX, aY) { 89 | // Find nearest Guide to float coordinates 90 | var i, d, m = Number.MAX_VALUE, best; 91 | for (i = 0; i < this.item.length; i++) { 92 | d = CA.distance(aX, aY, this.item[i].x, this.item[i].y); 93 | if (d < m) { 94 | m = d; 95 | best = this.item[i]; 96 | } 97 | } 98 | return { 99 | distance: m, 100 | item: best 101 | }; 102 | }; 103 | 104 | SC.Guides.prototype.addToPoints = function (aNetId, aPoints) { 105 | // To aPoints array, add all Guides with given net id 106 | var i; 107 | for (i = 0; i < this.item.length; i++) { 108 | if (this.item[i].net === aNetId) { 109 | aPoints.push({x: this.item[i].x, y: this.item[i].y}); 110 | } 111 | } 112 | }; 113 | 114 | SC.Guides.prototype.removeXYNet = function (aX, aY, aNet) { 115 | // Remove nearest Guide (within 1 cell) at given coordinates but only if same as net 116 | var i; 117 | for (i = this.item.length - 1; i >= 0; i--) { 118 | if (this.item[i].net === aNet) { 119 | if (CA.distance(aX, aY, this.item[i].x, this.item[i].y) <= 0.5) { 120 | this.item.splice(i, 1); 121 | } 122 | } 123 | } 124 | }; 125 | 126 | SC.Guides.prototype.removePinGuide = function (aComponent) { 127 | // After component is moved, if any pin is at Guide, remove the Guide to prevent the "ball" 128 | // I decided no to use this, it messed up accidental movement of components 129 | var i, p, pin, v; 130 | for (p = 0; p < aComponent.pin.length; p++) { 131 | pin = aComponent.pinXY(p); 132 | // check all Guides 133 | for (i = this.item.length - 1; i >= 0; i--) { 134 | v = this.item[i]; 135 | // same position as pin and same net 136 | if (v.x === pin.x && v.y === pin.y && v.net === aComponent.pinNet[p]) { 137 | //console.log('removing Guide', v); 138 | this.item.splice(i, 1); 139 | } 140 | } 141 | } 142 | }; 143 | 144 | SC.Guides.prototype.removeGuidesOnPins = function (aGuideCacheXY) { 145 | // After finish drawing one guide wire, check if any Guides were added at pins and if so remove them as there would be ball 146 | var k, c = aGuideCacheXY; 147 | for (k in c) { 148 | if (c.hasOwnProperty(k)) { 149 | if (SC.components.findByPin(c[k].x, c[k].y)) { 150 | this.remove(c[k]); 151 | } 152 | } 153 | } 154 | }; 155 | -------------------------------------------------------------------------------- /js/stripboard/netlist.js: -------------------------------------------------------------------------------- 1 | // Generate netlist from components 2 | "use strict"; 3 | // globals: document, window 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.netlist = function () { 8 | // Generate netlist from components 9 | var i, pots = {}, connectors = {}, n, pn, ps, pw, pe, 10 | missing = [], netlist = [], ground_net, 11 | unique = {}, errors = [], spst = {}, spdt = {}, dpdt = {}; 12 | // components 13 | for (i = 0; i < SC.components.item.length; i++) { 14 | if (!SC.components.item[i].toNetlist) { 15 | console.log(SC.components.item[i]); 16 | throw "Component has no toNetlist() " + SC.components.item[i].name; 17 | } 18 | n = SC.components.item[i].toNetlist(); 19 | if (n) { 20 | netlist.push(n); 21 | } 22 | } 23 | // all connectors 24 | for (i = 0; i < SC.components.item.length; i++) { 25 | // ground 26 | if (SC.components.item[i].name === 'Ground') { 27 | ground_net = SC.components.item[i].nets()[0]; 28 | } 29 | // general connector 30 | if (SC.components.item[i] instanceof SC.Connector || SC.components.item[i] instanceof SC.ConnectorBattery) { 31 | connectors[SC.components.item[i].name] = SC.components.item[i].nets()[0]; 32 | } 33 | // pots 34 | if (SC.components.item[i] instanceof SC.ConnectorPot) { 35 | n = SC.components.item[i].name; 36 | ps = SC.components.item[i].pot_start; 37 | pw = SC.components.item[i].pot_wiper; 38 | pe = SC.components.item[i].pot_end; 39 | pn = SC.components.item[i].nets()[0]; 40 | pots[n] = pots[n] || {pot_start: -1, pot_wiper: -1, pot_end: -1, value: 0}; 41 | if (ps) { 42 | pots[n].pot_start = pn; 43 | pots[n].value = pots[n].value || SC.components.item[i].value; 44 | } 45 | if (pw) { 46 | pots[n].pot_wiper = pn; 47 | pots[n].value = pots[n].value || SC.components.item[i].value; 48 | } 49 | if (pe) { 50 | pots[n].pot_end = pn; 51 | pots[n].value = pots[n].value || SC.components.item[i].value; 52 | } 53 | } 54 | // SPST switches 55 | if (SC.components.item[i] instanceof SC.ConnectorSwitchSPST) { 56 | n = SC.components.item[i].name; 57 | spst[n] = spst[n] || {}; 58 | spst[n][SC.components.item[i].pin] = SC.components.item[i].nets()[0]; 59 | } 60 | // SPDT switches 61 | if (SC.components.item[i] instanceof SC.ConnectorSwitchSPDT) { 62 | n = SC.components.item[i].name; 63 | spdt[n] = spdt[n] || {}; 64 | spdt[n][SC.components.item[i].pin] = SC.components.item[i].nets()[0]; 65 | } 66 | // DPDT switches 67 | if (SC.components.item[i] instanceof SC.ConnectorSwitchDPDT) { 68 | n = SC.components.item[i].name; 69 | dpdt[n] = dpdt[n] || {}; 70 | dpdt[n][SC.components.item[i].pin] = SC.components.item[i].nets()[0]; 71 | } 72 | } 73 | // pots 74 | for (n in pots) { 75 | if (pots.hasOwnProperty(n)) { 76 | netlist.push({type: 'potentiometer', name: n, value: pots[n].value, nets: [pots[n].pot_start, pots[n].pot_wiper, pots[n].pot_end]}); 77 | } 78 | } 79 | // spst 80 | for (n in spst) { 81 | if (spst.hasOwnProperty(n)) { 82 | netlist.push({type: 'spst', name: n, nets: [spst[n][1], spst[n][2]]}); 83 | } 84 | } 85 | // spdt 86 | for (n in spdt) { 87 | if (spdt.hasOwnProperty(n)) { 88 | netlist.push({type: 'spdt', name: n, nets: [spdt[n][1], spdt[n][2], spdt[n][3]]}); 89 | } 90 | } 91 | // dpdt 92 | for (n in dpdt) { 93 | if (dpdt.hasOwnProperty(n)) { 94 | netlist.push({type: SC.components.findByName(n).schematic, name: n, nets: [dpdt[n][1], dpdt[n][2], dpdt[n][3], dpdt[n][4], dpdt[n][5], dpdt[n][6]]}); 95 | } 96 | } 97 | // Find missing connectors 98 | if (!connectors.Input) { 99 | missing.push('Input'); 100 | } 101 | if (!connectors.Output) { 102 | missing.push('Output'); 103 | } 104 | if (!connectors.Ground) { 105 | missing.push('Ground'); 106 | } 107 | if (!connectors.U1 && !connectors.Battery) { 108 | missing.push('U1 or Battery'); 109 | } 110 | if (missing.length > 0) { 111 | console.warn('Missing connectors: ' + missing.join(', ')); 112 | } 113 | // check for duplicate names 114 | for (i = 0; i < netlist.length; i++) { 115 | if (unique[netlist[i].name]) { 116 | errors.push('Name "' + netlist[i].name + '" is not unique (use e.g. "' + netlist[i].name + '" and "' + netlist[i].name + '2")!'); 117 | } 118 | unique[netlist[i].name] = 1; 119 | } 120 | return { 121 | netlist: netlist, 122 | pots: pots, 123 | spst: spst, 124 | spdt: spdt, 125 | dpdt: dpdt, 126 | connectors: connectors, 127 | missing: missing, 128 | errors: errors, 129 | ground: ground_net 130 | }; 131 | }; 132 | 133 | -------------------------------------------------------------------------------- /js/schematic/share.js: -------------------------------------------------------------------------------- 1 | // Create shareable url with schematic data in url 2 | // linter: ngspicejs-lint 3 | /* global document, window, CA, URL, URLSearchParams, alert */ 4 | "use strict"; 5 | 6 | var SC = window.SC || {}; 7 | 8 | SC.shareableExport = function () { 9 | // Return shareable url for current schematic 10 | // chrome-extension://pjebgnafikcpkeheibmimmdlmfmfoabj/schematic.html?share=battery,U1,9,10,gnd,14,15,0,false|resistor,R1,220k,2,4,-4,14,3,false|resistor,R2,1k,4,10,-1,10,2,false|resistor,R3,470k,5,6,6,14,3,false|resistor,R4,22k,7,6,2,17,3,false|resistor,R5,100,7,gnd,-1,19,0,false|resistor,R6,2.2k,6,gnd,4,19,0,false|resistor,R7,2.2k,11,gnd,12,19,0,false|capacitor,C1,4.7u,1,2,-7,16,3,false|capacitor,C2,1u,4,5,2,14,3,false|capacitor,C3,4.7u,6,11,9,17,3,false|transistor_npn,T1,BC547,4,2,7,-2,16,0,false|transistor_pnp,T2,BC557,6,5,10,7,11,2,true|sinewave,U2,10mV 196Hz,1,gnd,-8,19,0,false|ground,Ground,,gnd,14,22,0,false 11 | var prefix = new URL(document.location); 12 | prefix.search = ''; 13 | prefix = prefix.toString(); 14 | // components 15 | var params = SC.components.item.map((c) => { 16 | return [c.type, c.name, c.value, c.pinNet.join(','), c.x, c.y, c.rotate, c.mirror ? 1 : 0].join(','); 17 | }).concat( 18 | // guides 19 | SC.guides.item.map((g) => ['g',g.net,g.x,g.y].join(',')) 20 | ).join('|'); 21 | 22 | //SC.guides.item.map(g => ['g', g.net, g.x, g.y].join(',')) 23 | 24 | //console.log(prefix + '?share=' + params); 25 | return prefix + '?share=' + params; 26 | }; 27 | 28 | SC.shareableImport = function () { 29 | // Import schematic from url ?share=resistor,R1,10k,vcc,out,10,10,3,0|resistor,R2,20k,out,0,20,10,3,0| 30 | var params = new URLSearchParams(document.location.search); 31 | var s = params.get('share'); 32 | if (!s) { 33 | return; 34 | } 35 | //var net_count = SC.specsPin.transistor_npn.length 36 | s = s.split('|').map((a) => a.split(',')); 37 | //console.log(s); 38 | var ret = []; 39 | var has_position = false; 40 | s.forEach((a) => { 41 | // guides 42 | if (a[0] === 'g') { 43 | return; 44 | } 45 | // components 46 | if (!SC.specsPin[a[0]]) { 47 | alert('Shared url has unknown component: ' + a[0]); 48 | throw "Shared import failed"; 49 | } 50 | var n = SC.specsPin[a[0]].length; 51 | //console.warn(a, n, 3 + n); 52 | if ((a.length <= 3 + n) || (a[0] === 'inductor_coupling')) { 53 | a.push(0); 54 | a.push(0); 55 | a.push(0); 56 | a.push(0); 57 | } else { 58 | has_position = true; 59 | } 60 | ret.push({ 61 | type: a[0], 62 | name: a[1], 63 | value: a[2], 64 | nets: a.slice(3, 3 + n), 65 | x: parseInt(a[3 + n], 10) || 0, 66 | y: parseInt(a[3 + n + 1], 10) || 0, 67 | rotate: parseInt(a[3 + n + 2], 10) || 0, 68 | mirror: a[3 + n + 3] === '1' ? true : false 69 | }); 70 | }); 71 | //console.log(ret); 72 | SC.netlistImport(ret); 73 | s.forEach((a) => { 74 | if (a[0] === 'g') { 75 | SC.guides.item.push(new SC.Guide(a[1], parseInt(a[2], 10), parseInt(a[3], 10))); 76 | } 77 | }); 78 | //console.log(SC.guides.item); 79 | SC.filename = 'shared_shematic'; 80 | SC.v.zoom = 20; 81 | var c = SC.components.center(); 82 | SC.v.centerTo(c.x, c.y); 83 | SC.render(); 84 | 85 | // if url does not contain positions, spread components 86 | if (!has_position) { 87 | SC.onSpread(); 88 | var old = {item: [ 89 | {name: 'IN', x: -13, y: 10, rotate: 2, mirror: false}, 90 | {name: 'CIN', x: -10, y: 10, rotate: 3, mirror: false}, 91 | {name: 'V2', x: 23, y: 8, rotate: 0, mirror: false}, 92 | {name: 'Ground', x: 23, y: 19, rotate: 0, mirror: false}, 93 | {name: 'COUT', x: 17, y: 12, rotate: 3, mirror: false}, 94 | {name: 'OUT', x: 21, y: 12, rotate: 0, mirror: false} 95 | ]}; 96 | var is_interface = {}; 97 | old.item.forEach((o) => { 98 | var e = SC.components.findByName(o.name); 99 | if (e) { 100 | e.restoreFromComponents(old); 101 | is_interface[o.name] = true; 102 | } 103 | }); 104 | SC.autoplace2(is_interface, {minx: -7, miny: -2, maxx: 16, maxy: 22}); 105 | var cen = SC.components.center(); 106 | SC.v.centerTo(cen.x, cen.y); 107 | SC.render(); 108 | } 109 | // redirect to same page without url params so that user can reload page 110 | if (params.get('reload') !== 'false') { 111 | document.location = 'schematic.html'; 112 | } 113 | }; 114 | 115 | SC.onShare = function () { 116 | // Show dialog with shareable url 117 | var a = document.createElement('a'); 118 | a.href = SC.shareableExport(); 119 | a.textContent = a.href; 120 | var d = CA.modalDialog('Share schematic URL', '', ['Copy to clipboard'], function (aButton) { 121 | if (aButton === 'Copy to clipboard') { 122 | CA.copyToClipboard(a.href); 123 | } 124 | }); 125 | d.content.style.margin = '1ex'; 126 | d.content.style.maxWidth = '75vw'; 127 | d.content.style.overflowWrap = 'anywhere'; 128 | d.content.appendChild(a); 129 | }; 130 | 131 | 132 | -------------------------------------------------------------------------------- /js/stripboard/components.js: -------------------------------------------------------------------------------- 1 | // All components 2 | "use strict"; 3 | // globals: document, window 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.Components = function () { 8 | this.item = []; 9 | }; 10 | 11 | SC.Components.prototype.toObject = function () { 12 | // Return exportable object 13 | var i, o = {}; 14 | o.item = []; 15 | for (i = 0; i < this.item.length; i++) { 16 | o.item.push(this.item[i].toObject()); 17 | } 18 | return o; 19 | }; 20 | 21 | SC.Components.prototype.fromObject = function (aObject) { 22 | // Set this from previously exported object 23 | var i, c, t; 24 | this.item = []; 25 | if (aObject.item) { 26 | for (i = 0; i < aObject.item.length; i++) { 27 | t = aObject.item[i].type; 28 | if (!SC.factory[t]) { 29 | throw 'No factory for ' + t; 30 | } 31 | c = new SC.factory[t](); 32 | c.fromObject(aObject.item[i]); 33 | this.item.push(c); 34 | } 35 | } 36 | }; 37 | 38 | SC.Components.prototype.add = function (aType, aX, aY) { 39 | // Add component of given type at given position 40 | var c; 41 | if (!SC.factory[aType]) { 42 | throw "No factory for " + aType; 43 | } 44 | c = new SC.factory[aType](SC.components.nextName(aType), aX, aY); 45 | c.type = aType; 46 | this.item.push(c); 47 | SC.grid.updateNets(); 48 | return c; 49 | }; 50 | 51 | SC.Components.prototype.remove = function (aComponent) { 52 | // Remove component 53 | var i = this.item.indexOf(aComponent); 54 | if (i >= 0) { 55 | this.item.splice(i, 1); 56 | } 57 | SC.grid.updateNets(); 58 | }; 59 | 60 | SC.Components.prototype.render = function (aContext) { 61 | // Render all components 62 | var i; 63 | for (i = 0; i < this.item.length; i++) { 64 | if (SC.show.components || (this.item[i] instanceof SC.Link)) { 65 | this.item[i].render(aContext); 66 | } 67 | } 68 | }; 69 | 70 | SC.Components.prototype.nextName = function (aType) { 71 | // Assign next available name for component of given type 72 | if (SC.namingPrefix[aType] === '') { 73 | return ''; 74 | } 75 | var i, c, m = 0; 76 | for (i = 0; i < this.item.length; i++) { 77 | if (SC.namingPrefix[this.item[i].type] === SC.namingPrefix[aType]) { 78 | c = this.item[i].name.match(/[0-9]+/); 79 | if (c) { 80 | m = Math.max(m, parseInt(c[0], 10)); 81 | } 82 | } 83 | } 84 | return (SC.namingPrefix[aType] || 'X') + (m + 1); 85 | }; 86 | 87 | SC.Components.prototype.findByName = function (aName) { 88 | // Find first component with this name 89 | var i; 90 | for (i = 0; i < this.item.length; i++) { 91 | if (this.item[i].name === aName) { 92 | return this.item[i]; 93 | } 94 | } 95 | }; 96 | 97 | SC.Components.prototype.findByXY = function (aX, aY) { 98 | // Find component that has pin at given coordinates 99 | var i; 100 | for (i = 0; i < this.item.length; i++) { 101 | if (this.item[i].hasPin(aX, aY)) { 102 | return this.item[i]; 103 | } 104 | } 105 | }; 106 | 107 | SC.Components.prototype.findNearest = function (aX, aY, aClass) { 108 | // Find component nearest to given coords, optional filter for class 109 | var i, d, m = Number.MAX_VALUE, best; 110 | for (i = 0; i < this.item.length; i++) { 111 | if (aClass && !(this.item[i] instanceof aClass)) { 112 | continue; 113 | } 114 | d = this.item[i].distanceTo(aX, aY); 115 | if (d < m) { 116 | m = d; 117 | best = this.item[i]; 118 | } 119 | } 120 | return { 121 | distance: m, 122 | component: best 123 | }; 124 | }; 125 | 126 | SC.Components.prototype.groundNet = function () { 127 | // Find ground net id 128 | var i; 129 | for (i = 0; i < this.item.length; i++) { 130 | if (this.item[i] instanceof SC.Connector && this.item[i].name === 'Ground') { 131 | return this.item[i].nets()[0]; 132 | } 133 | } 134 | }; 135 | 136 | SC.Components.prototype.fixPinOrder = function (aComponent, aDialog) { 137 | // For components that have x1,y1,x2,y2 make sure x1,y1 is on board and x2,y2 outside 138 | var x, y; 139 | // must start on board 140 | if (!SC.grid.onBoard(aComponent.x1, aComponent.y1)) { 141 | x = aComponent.x1; 142 | y = aComponent.y1; 143 | aComponent.x1 = aComponent.x2; 144 | aComponent.y1 = aComponent.y2; 145 | aComponent.x2 = x; 146 | aComponent.y2 = y; 147 | } 148 | if (!SC.grid.onBoard(aComponent.x1, aComponent.y1)) { 149 | if (aDialog) { 150 | alert('Connector must START on the board'); 151 | } 152 | return false; 153 | } 154 | if (SC.grid.onBoard(aComponent.x2, aComponent.y2)) { 155 | if (aDialog) { 156 | alert('Connector must END outside the board'); 157 | } 158 | return false; 159 | } 160 | //console.log(aComponent.x1, aComponent.y1, aComponent.x2, aComponent.y2); 161 | return true; 162 | }; 163 | 164 | SC.Components.prototype.sameNameUpdateProperty = function (aName, aProperty, aValue) { 165 | // For switch connectors update property of a components with the same values 166 | var i; 167 | for (i = 0; i < this.item.length; i++) { 168 | if (this.item[i].name === aName) { 169 | this.item[i][aProperty] = aValue; 170 | } 171 | } 172 | }; 173 | 174 | -------------------------------------------------------------------------------- /js/stripboard/component_connector_pot.js: -------------------------------------------------------------------------------- 1 | // Single pin of external potentiometer (one of 3) connected to the edge of the stripboard 2 | "use strict"; 3 | // globals: document, window, CA 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.ConnectorPot = function (aName, aX1, aY1, aX2, aY2) { 8 | this.is_connector = true; 9 | this.type = 'connector_pot'; 10 | this.name = aName; 11 | this.x1 = aX1 || 0; 12 | this.y1 = aY1 || 0; 13 | this.x2 = aX2 || 0; 14 | this.y2 = aY2 || 0; 15 | this.value = '50k'; 16 | this.pot_start = false; 17 | this.pot_wiper = false; 18 | this.pot_end = false; 19 | }; 20 | 21 | SC.factory.connector_pot = SC.ConnectorPot; 22 | SC.namingPrefix.connector = 'P'; 23 | 24 | SC.ConnectorPot.prototype.toObject = function () { 25 | // Return exportable object 26 | return { 27 | type: this.type, 28 | name: this.name, 29 | x1: this.x1, 30 | y1: this.y1, 31 | x2: this.x2, 32 | y2: this.y2, 33 | pot_start: this.pot_start, 34 | pot_wiper: this.pot_wiper, 35 | pot_end: this.pot_end, 36 | value: this.value 37 | }; 38 | }; 39 | 40 | SC.ConnectorPot.prototype.fromObject = function (aObject) { 41 | // Set this from previously exported object 42 | this.name = aObject.name; 43 | this.x1 = aObject.x1; 44 | this.y1 = aObject.y1; 45 | this.x2 = aObject.x2; 46 | this.y2 = aObject.y2; 47 | this.pot_start = aObject.pot_start; 48 | this.pot_wiper = aObject.pot_wiper; 49 | this.pot_end = aObject.pot_end; 50 | this.value = aObject.value; 51 | }; 52 | 53 | SC.ConnectorPot.prototype.toNetlist = function () { 54 | // Netlist of external pots are made by merging 2~3 ConnectorPot in netlist.js 55 | return; 56 | }; 57 | 58 | SC.ConnectorPot.prototype.nets = function () { 59 | // Return nets connected to pins 60 | return [ 61 | SC.grid.net(this.x1, this.y1) 62 | ]; 63 | }; 64 | 65 | SC.ConnectorPot.prototype.hasPin = function (aX, aY) { 66 | // Return true if given coords are at any of the pins 67 | return (this.x1 === aX) && (this.y1 === aY); 68 | }; 69 | 70 | SC.ConnectorPot.prototype.fixPinOrder = function (aDialog) { 71 | // Connector must start on board and end outside board, if it is other way around fix it 72 | return SC.components.fixPinOrder(this, aDialog); 73 | }; 74 | 75 | SC.ConnectorPot.prototype.distanceTo = function (aX, aY) { 76 | // Return distance to given point on grid 77 | return CA.distancePointLineSegment(aX, aY, this.x1, this.y1, this.x2, this.y2); 78 | }; 79 | 80 | SC.ConnectorPot.prototype.render = function (aContext) { 81 | // Render connector 82 | var a = SC.grid.gridToScreen(this.x1, this.y1, true), 83 | b = SC.grid.gridToScreen(this.x2, this.y2, true), 84 | px, 85 | py, 86 | line_width = 4 * SC.v.zoom, 87 | pot = ' ' + (this.pot_start ? '1=start ' : '') + (this.pot_wiper ? '2=wiper ' : '') + (this.pot_end ? '3=end ' : ''); 88 | if (this.value) { 89 | pot += ' ' + this.value + 'Ω'; 90 | } 91 | // name + pot pins 92 | aContext.globalAlpha = 1; 93 | aContext.font = 'bold 12px sans-serif'; 94 | aContext.fillStyle = 'black'; 95 | aContext.lineWidth = 1; 96 | aContext.strokeStyle = 'white'; 97 | aContext.textBaseline = 'middle'; 98 | if (this.x1 === 0) { 99 | aContext.textAlign = 'right'; 100 | aContext.strokeText(this.name + pot + ' ', b.x - line_width - (2 * line_width - 4), b.y); 101 | aContext.fillText(this.name + pot + ' ', b.x - line_width - (2 * line_width - 4), b.y); 102 | px = b.x - 2 * line_width - line_width; 103 | py = Math.round(b.y - line_width); 104 | } else { 105 | aContext.textAlign = 'left'; 106 | aContext.strokeText(this.name + pot, b.x + line_width + (2 * line_width + 4), b.y); 107 | aContext.fillText(this.name + pot, b.x + line_width + (2 * line_width + 4), b.y); 108 | px = b.x + line_width; 109 | py = Math.round(b.y - line_width); 110 | } 111 | // line 112 | aContext.globalAlpha = 0.5; 113 | aContext.lineCap = 'round'; 114 | aContext.lineWidth = line_width; 115 | aContext.strokeStyle = 'black'; 116 | aContext.beginPath(); 117 | aContext.moveTo(a.x, a.y); 118 | aContext.lineTo(b.x, b.y); 119 | aContext.stroke(); 120 | aContext.globalAlpha = 1; 121 | aContext.fillStyle = '#ff000077'; 122 | // mini pot images 123 | if (this.pot_start) { 124 | aContext.drawImage(SC.image.pot_start, px, py, 2 * line_width, 2 * line_width); 125 | } 126 | if (this.pot_wiper) { 127 | aContext.drawImage(SC.image.pot_wiper, px, py, 2 * line_width, 2 * line_width); 128 | } 129 | if (this.pot_end) { 130 | aContext.drawImage(SC.image.pot_end, px, py, 2 * line_width, 2 * line_width); 131 | } 132 | }; 133 | 134 | SC.ConnectorPot.prototype.propertiesDialog = function (aCallback) { 135 | // Show properties dialog 136 | var t = this, d = SC.propertiesDialog(this, 'Ω', function (aButton) { 137 | aCallback(aButton); 138 | if (aButton === 'Save') { 139 | t.pot_start = d.pot_start.input.checked; 140 | t.pot_wiper = d.pot_wiper.input.checked; 141 | t.pot_end = d.pot_end.input.checked; 142 | } 143 | SC.render(); 144 | }); 145 | d.dlg.h1.textContent = 'Connector for pot'; 146 | d.value.label.textContent = 'Resistance'; 147 | d.pot_start = CA.labelCheckboxLabel(d.dlg.content, 'Pin 1', this.pot_start, 'start'); 148 | d.pot_wiper = CA.labelCheckboxLabel(d.dlg.content, 'Pin 2', this.pot_wiper, 'wiper'); 149 | d.pot_end = CA.labelCheckboxLabel(d.dlg.content, 'Pin 3', this.pot_end, 'end'); 150 | return d; 151 | }; 152 | 153 | -------------------------------------------------------------------------------- /js/schematic/net.js: -------------------------------------------------------------------------------- 1 | // Single net (connections between multiple components sharing the same netlist number) 2 | "use strict"; 3 | // globals: document, window, CA, vec2 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.Net = function (aId) { 8 | // Constructor 9 | this.id = aId; 10 | this.pins = []; // {component: SC.Component, pin: 0} 11 | }; 12 | 13 | SC.Net.prototype.toObject = function () { 14 | // Return exportable object 15 | var i, a = []; 16 | for (i = 0; i < this.pins.length; i++) { 17 | a.push({ 18 | component: this.pins[i].component.name, 19 | pin: this.pins[i].pin 20 | }); 21 | } 22 | return { 23 | id: this.id, 24 | pins: a 25 | }; 26 | }; 27 | 28 | SC.Net.prototype.fromObject = function (aObject) { 29 | // Set this from previously exported object 30 | this.id = aObject.id; 31 | this.pins = []; 32 | var i; 33 | for (i = 0; i < aObject.pins.length; i++) { 34 | this.pins.push({ 35 | component: SC.components.findByName(aObject.pins[i].component), 36 | pin: aObject.pins[i].pin 37 | }); 38 | } 39 | }; 40 | 41 | SC.Net.prototype.drawDotsWhere3Intersects = function (aContext, aSeg, aPinPoints) { //, aWhichNet 42 | // Draw dot where 3 or more segments meet 43 | //console.log(aSeg, aPinPoints, aWhichNet); 44 | var size = 4 * SC.v.zoom / SC.gridSize, 45 | i, 46 | p, 47 | k, 48 | ends = {}; 49 | for (i = 0; i < aSeg.length; i++) { 50 | // start 51 | k = aSeg[i].x1 + '_' + aSeg[i].y1; 52 | ends[k] = ends[k] || { 53 | count: 0, 54 | x: aSeg[i].x1, 55 | y: aSeg[i].y1, 56 | debug: 'start' 57 | }; 58 | ends[k].count++; 59 | // end 60 | k = aSeg[i].x2 + '_' + aSeg[i].y2; 61 | ends[k] = ends[k] || { 62 | count: 0, 63 | x: aSeg[i].x2, 64 | y: aSeg[i].y2, 65 | debug: 'end' 66 | }; 67 | ends[k].count++; 68 | } 69 | // add pin points (so that resistor attaching to 2 segments will cause ball) 70 | for (i = 0; i < aPinPoints.length; i++) { 71 | p = { 72 | x: aPinPoints[i].x, 73 | y: aPinPoints[i].y 74 | }; 75 | k = p.x + '_' + p.y; 76 | ends[k] = ends[k] || { 77 | count: 0, 78 | x: p.x, 79 | y: p.y, 80 | debug: 'pin' 81 | }; 82 | ends[k].count++; 83 | } 84 | // ball at >=3 intersections 85 | if (aContext) { 86 | aContext.globalAlpha = 1; 87 | aContext.fillStyle = 'black'; 88 | for (k in ends) { 89 | if (ends.hasOwnProperty(k)) { 90 | if (ends[k].count >= 3) { 91 | p = SC.v.canvasToScreen(ends[k].x, ends[k].y); 92 | aContext.beginPath(); 93 | aContext.arc(p.x, p.y, size, 0, 2 * Math.PI, false); 94 | aContext.fill(); 95 | } 96 | } 97 | } 98 | } 99 | /* 100 | if (aWhichNet === 4) { 101 | console.log(ends, aSeg, aPinPoints); 102 | } 103 | */ 104 | }; 105 | 106 | SC.Net.prototype.render = function (aContext) { 107 | // Render this net 108 | var i, s, a, b, pt = [], len = 0, 109 | kr, cpins = [], 110 | lw = 2 * SC.v.zoom / SC.gridSize; 111 | // find all component pins 112 | for (i = 0; i < this.pins.length; i++) { 113 | s = this.pins[i].component.pinXY(this.pins[i].pin, false); 114 | pt.push(s); 115 | cpins.push(s); 116 | } 117 | // include guides 118 | SC.guides.addToPoints(this.id, pt); 119 | // find minimum spanning tree 120 | kr = window.kruskalCoords(pt); 121 | this.kruskal = kr; 122 | // choose color 123 | if (typeof this.id === 'string') { 124 | a = 0; 125 | for (i = 0; i < this.id.length; i++) { 126 | a += this.id.charCodeAt(i) << 7; 127 | } 128 | this.color = SC.palette[a % SC.palette.length]; 129 | } else { 130 | this.color = SC.palette[this.id % SC.palette.length]; 131 | } 132 | // draw segments 133 | if (aContext) { 134 | aContext.font = '10px sans-serif'; 135 | aContext.globalAlpha = 1; 136 | aContext.lineWidth = lw; 137 | aContext.strokeStyle = SC.show.net_black ? 'black' : this.color; 138 | aContext.fillStyle = SC.show.net_black ? 'black' : this.color; 139 | for (i = 0; i < kr.length; i++) { 140 | a = SC.v.canvasToScreen(kr[i].x1, kr[i].y1); 141 | b = SC.v.canvasToScreen(kr[i].x2, kr[i].y2); 142 | len += CA.distance(kr[i].x1, kr[i].y1, kr[i].x2, kr[i].y2); 143 | aContext.beginPath(); 144 | aContext.moveTo(a.x, a.y); 145 | aContext.lineTo(b.x, b.y); 146 | aContext.stroke(); 147 | //if (SC.show.net_numbers) { 148 | //aContext.fillText('n' + this.id, (a.x + b.x) / 2 + 4, (a.y + b.y) / 2); 149 | //} 150 | } 151 | // draw dots in intersections 152 | this.drawDotsWhere3Intersects(aContext, kr, cpins, this.id); 153 | } else { 154 | for (i = 0; i < kr.length; i++) { 155 | len += CA.distance(kr[i].x1, kr[i].y1, kr[i].x2, kr[i].y2); 156 | } 157 | } 158 | return len; 159 | }; 160 | 161 | SC.Net.prototype.distanceTo = function (aX, aY) { 162 | // Return nearest distance of point to any kruskal segment of this net 163 | var d, m = Number.MAX_VALUE, 164 | i, k; 165 | for (i = 0; i < this.kruskal.length; i++) { 166 | k = this.kruskal[i]; 167 | d = CA.distancePointLineSegment(aX, aY, k.x1, k.y1, k.x2, k.y2); 168 | m = Math.min(d, m); 169 | } 170 | return m; 171 | }; 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /js/kruskal/kruskal.js: -------------------------------------------------------------------------------- 1 | // Kruskal algorithm for finding minimum spaning tree 2 | // https://github.com/girls-incode/graph-minimum-spaning-tree-MST-kruskal 3 | "use strict"; 4 | (function() { 5 | 6 | class Edge { 7 | constructor(v1, v2, w = 0) { 8 | this.v1 = v1; 9 | this.v2 = v2; 10 | this.w = w; 11 | } 12 | } 13 | 14 | class Graph { 15 | constructor(v, e) { 16 | this.v = v; 17 | this.e = e; 18 | this.edges = []; 19 | this.nodes = []; 20 | } 21 | 22 | addEdge(edge) { 23 | this.edges.push(edge); 24 | if (!this.nodes.includes(edge.v1)) { 25 | this.nodes.push(edge.v1); 26 | } 27 | if (!this.nodes.includes(edge.v2)) { 28 | this.nodes.push(edge.v2); 29 | } 30 | } 31 | 32 | getEdge(pos) { 33 | return this.edges[pos] 34 | } 35 | 36 | getEdges() { 37 | return this.edges 38 | } 39 | 40 | getNodes() { 41 | return this.nodes 42 | } 43 | 44 | // get the root of node 45 | find(subsets, node) { 46 | let nodeInfo = subsets.get(node); 47 | if (nodeInfo.parent != node) { 48 | nodeInfo.parent = this.find(subsets, nodeInfo.parent) 49 | } 50 | 51 | return nodeInfo.parent; 52 | } 53 | 54 | // unite the x and y subsets based on rank 55 | union(subsets, x, y) { 56 | let xroot = this.find(subsets, x); 57 | let yroot = this.find(subsets, y); 58 | 59 | if (subsets.get(xroot).rank < subsets.get(yroot).rank) { 60 | subsets.get(xroot).parent = yroot; 61 | } else if (subsets.get(xroot).rank > subsets.get(yroot).rank) { 62 | subsets.get(yroot).parent = xroot; 63 | } else { 64 | subsets.get(yroot).parent = xroot; 65 | subsets.get(xroot).rank++; 66 | } 67 | } 68 | } 69 | 70 | function kruskal(gNodes, gEdges, gFrom, gTo, gWeight) { 71 | let i = 0, 72 | j = 0, 73 | cost = 0; 74 | let subsets = new Map(), 75 | result = []; 76 | 77 | let graph = new Graph(gNodes, gEdges); 78 | 79 | while (i < gEdges) { 80 | graph.addEdge(new Edge(gFrom[i], gTo[i], gWeight[i])); 81 | i++; 82 | } 83 | 84 | graph.getEdges().sort((edge1, edge2) => { 85 | if (edge1.w === edge2.w) { 86 | return 1; 87 | } 88 | 89 | return edge1.w < edge2.w ? -1 : 1; 90 | }); 91 | 92 | //console.log('sorted edges:' , graph.getEdges()); 93 | 94 | graph.getNodes().forEach(node => { 95 | subsets.set(node, { 96 | parent: node, 97 | rank: 0 98 | }); 99 | }); 100 | 101 | i = 0; 102 | while (j < gNodes - 1) { 103 | let edge = graph.getEdge(i++); 104 | let root1 = graph.find(subsets, edge.v1); 105 | let root2 = graph.find(subsets, edge.v2); 106 | 107 | // if the nodes doesn't create a cycle then we add the edge to final subgraph 108 | if (root1 != root2) { 109 | result[j++] = edge; 110 | // update the total weight of the subgraph 111 | cost += edge.w; 112 | graph.union(subsets, root1, root2); 113 | } 114 | } 115 | 116 | var ret = []; 117 | i = 0; 118 | while (i < j) { 119 | ret.push({ 120 | a: result[i].v1, 121 | b: result[i].v2 122 | }); 123 | i++; 124 | //console.log(`${result[i].v1} -- ${result[i].v2} ( ${result[i++].w} )`); 125 | } 126 | 127 | //console.log('time: ', cost, ret); 128 | return { 129 | cost: cost, 130 | tree: ret 131 | }; 132 | } 133 | window.kruskal = kruskal; 134 | 135 | window.kruskalCoords = function(aCoords) { 136 | // Boilerplace to Call kruskal on given coordinates [{x,y},{x,y},{x,y},...] 137 | // returns [{x1,y1,x2,y2}, {x1,y1,x2,y2}, {x1,y1,x2,y2}, ...] 138 | //console.log(aCoords); 139 | var uni = {}, 140 | vfrom = [], 141 | vto = [], 142 | cost = [], 143 | ret, a, p1, p2, j, k, b, dx, dy, d; 144 | for (j in aCoords) { 145 | if (aCoords.hasOwnProperty(j)) { 146 | for (k in aCoords) { 147 | if (aCoords.hasOwnProperty(k)) { 148 | uni[j] = 1; 149 | uni[k] = 1; 150 | if (j !== k) { 151 | a = aCoords[j]; 152 | b = aCoords[k]; 153 | dx = a.x - b.x; 154 | dy = a.y - b.y; 155 | d = Math.sqrt(dx * dx + dy * dy); 156 | vfrom.push(j); 157 | vto.push(k); 158 | cost.push(d); 159 | } 160 | } 161 | } 162 | } 163 | } 164 | ret = window.kruskal(Object.keys(uni).length, cost.length, vfrom, vto, cost); 165 | a = []; 166 | for (k in ret.tree) { 167 | if (ret.tree.hasOwnProperty(k)) { 168 | p1 = aCoords[ret.tree[k].a]; 169 | p2 = aCoords[ret.tree[k].b]; 170 | a.push({ 171 | x1: p1.x, 172 | y1: p1.y, 173 | x2: p2.x, 174 | y2: p2.y 175 | }); 176 | } 177 | } 178 | //console.log(a); 179 | return a; 180 | }; 181 | 182 | }()); 183 | -------------------------------------------------------------------------------- /js/stripboard/component_trimmer.js: -------------------------------------------------------------------------------- 1 | // Trimmer pot directly on stripboard, not leads on the edge, use F to flip 2 | "use strict"; 3 | // globals: document, window, CA, vec2 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.Trimmer = function (aName, aX1, aY1, aX2, aY2, aInline) { 8 | this.type = 'trimmer'; 9 | this.name = aName; 10 | this.x1 = aX1 || 0; 11 | this.y1 = aY1 || 0; 12 | this.x2 = aX2 || 0; 13 | this.y2 = aY2 || 0; 14 | this.value = '50k'; 15 | this.inline = false; 16 | }; 17 | 18 | SC.factory.trimmer = SC.Trimmer; 19 | SC.namingPrefix.trimmer = 'P'; 20 | 21 | SC.Trimmer.prototype.toObject = function () { 22 | // Return exportable object 23 | return { 24 | type: this.type, 25 | name: this.name, 26 | x1: this.x1, 27 | y1: this.y1, 28 | x2: this.x2, 29 | y2: this.y2, 30 | value: this.value, 31 | inline: this.inline 32 | }; 33 | }; 34 | 35 | SC.Trimmer.prototype.fromObject = function (aObject) { 36 | // Set this from previously exported object 37 | this.type = aObject.type; 38 | this.name = aObject.name; 39 | this.x1 = aObject.x1; 40 | this.y1 = aObject.y1; 41 | this.x2 = aObject.x2; 42 | this.y2 = aObject.y2; 43 | this.value = aObject.value; 44 | this.inline = aObject.inline; 45 | }; 46 | 47 | SC.Trimmer.prototype.validator = function () { 48 | // Return true if Trimmer spans exactly 3 holes 49 | return CA.distance(this.x1, this.y1, this.x2, this.y2) === 2; 50 | }; 51 | 52 | SC.Trimmer.prototype.toNetlist = function () { 53 | // Return netlist object with correct pin order C-B-E 54 | return { 55 | type: 'potentiometer', 56 | name: this.name, 57 | value: this.value, 58 | nets: this.nets() 59 | }; 60 | }; 61 | 62 | SC.Trimmer.prototype.wiper = function () { 63 | // Return wiper coords 64 | var a = vec2(this.x1, this.y1), 65 | b = vec2(this.x2, this.y2), 66 | s = b.sub(a); 67 | if (this.inline) { 68 | return a.add(b).mul(0.5).round(); 69 | } 70 | return a.add(s.mul(0.5)).add(s.right()).round(); 71 | }; 72 | 73 | SC.Trimmer.prototype.center = function () { 74 | // Return center coords 75 | var a = vec2(this.x1, this.y1), 76 | b = vec2(this.x2, this.y2), 77 | s = b.sub(a); 78 | if (this.inline) { 79 | return a.add(b).mul(0.5).round(); 80 | } 81 | return a.add(s.mul(0.5)).add(s.right().mul(0.5)); 82 | }; 83 | 84 | SC.Trimmer.prototype.nets = function () { 85 | // Return nets connected to pins 86 | var w = this.wiper(); 87 | return [ 88 | SC.grid.net(this.x1, this.y1), 89 | SC.grid.net(w.x, w.y), 90 | SC.grid.net(this.x2, this.y2) 91 | ]; 92 | }; 93 | 94 | SC.Trimmer.prototype.flip = function () { 95 | // Flip pins (polarity) 96 | [this.x1, this.y1, this.x2, this.y2] = [this.x2, this.y2, this.x1, this.y1]; 97 | }; 98 | 99 | SC.Trimmer.prototype.hasPin = function (aX, aY) { 100 | // Return true if given coords are at any of the pins 101 | var w = this.wiper(); 102 | return (this.x1 === aX && this.y1 === aY) 103 | || (this.x2 === aX && this.y2 === aY) 104 | || (w.x === aX && w.y === aY); 105 | }; 106 | 107 | SC.Trimmer.prototype.distanceTo = function (aX, aY) { 108 | // Return distance to given point on grid 109 | var w = this.wiper(); 110 | return Math.min( 111 | CA.distancePointLineSegment(aX, aY, this.x1, this.y1, this.x2, this.y2), 112 | CA.distancePointLineSegment(aX, aY, this.x1, this.y1, w.x, w.y), 113 | CA.distancePointLineSegment(aX, aY, this.x2, this.y2, w.x, w.y) 114 | ); 115 | }; 116 | 117 | SC.Trimmer.prototype.render = function (aContext) { 118 | // Render component 119 | var a = SC.grid.gridToScreen(this.x1, this.y1), 120 | b = SC.grid.gridToScreen(this.x2, this.y2), 121 | c = this.center(), 122 | w = this.wiper(); 123 | c = SC.grid.gridToScreen(c.x, c.y); 124 | w = SC.grid.gridToScreen(w.x, w.y); 125 | // check length 126 | if (CA.distance(this.x1, this.y1, this.x2, this.y2) % 2 !== 0) { 127 | aContext.strokeStyle = 'black'; 128 | aContext.fillStyle = 'blue'; 129 | aContext.font = 'bold 12px sans-serif'; 130 | aContext.textBaseline = 'middle'; 131 | aContext.textAlign = 'center'; 132 | aContext.fillText('Trimmer must span exactly 3 holes', c.x, c.y); 133 | aContext.beginPath(); 134 | aContext.moveTo(a.x, a.y); 135 | aContext.lineTo(b.x, b.y); 136 | aContext.stroke(); 137 | return; 138 | } 139 | // package 140 | if (this.inline) { 141 | SC.renderDefault(aContext, this.x1, this.y1, this.x2, this.y2, SC.image.trimmer_inline, '', '', 43, 42); 142 | } else { 143 | SC.renderDefault(aContext, this.x1, this.y1, this.x2, this.y2, SC.image.trimmer, '', '', 30, 22); 144 | } 145 | // name + value 146 | aContext.globalAlpha = 1; 147 | aContext.strokeStyle = 'black'; 148 | aContext.lineWidth = 2; 149 | aContext.font = 'bold 12px sans-serif'; 150 | aContext.textBaseline = 'bottom'; 151 | aContext.textAlign = 'center'; 152 | aContext.fillStyle = 'white'; 153 | aContext.strokeText(this.name, c.x, c.y - 2 * SC.v.zoom); 154 | aContext.fillText(this.name, c.x, c.y - 2 * SC.v.zoom); 155 | aContext.textBaseline = 'top'; 156 | aContext.strokeText(this.value, c.x, c.y + 2 * SC.v.zoom); 157 | aContext.fillText(this.value, c.x, c.y + 2 * SC.v.zoom); 158 | }; 159 | 160 | SC.Trimmer.prototype.propertiesDialog = function (aCallback) { 161 | // Show properties dialog 162 | var t = this, a, d = SC.propertiesDialog(this, 'Ω', function (aButton) { 163 | if (aButton === 'Save') { 164 | t.inline = a.input.checked; 165 | } 166 | aCallback(aButton); 167 | }); 168 | a = CA.labelCheckboxLabel(d.dlg.content, 'Inline', this.inline, '(multiturn standing trimmer)'); 169 | a.div.title = 'Inline means standing multiturn pot with pins in straight line'; 170 | }; 171 | 172 | -------------------------------------------------------------------------------- /js/schematic/component.js: -------------------------------------------------------------------------------- 1 | // Single universal component (unlike stripboard where every component behaves slightly differently, in schematic component is just image with pins) 2 | "use strict"; 3 | // globals: document, window, CA, vec2 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.Component = function (aType, aName, aValue, aX, aY, aRotate, aMirror) { 8 | // Constructor 9 | this.type = aType || 'resistor'; 10 | this.name = aName; 11 | this.value = aValue; 12 | this.x = aX || 0; 13 | this.y = aY || 0; 14 | this.rotate = aRotate || 0; 15 | this.mirror = aMirror || false; 16 | this.pinNet = []; 17 | this.updatePins(); 18 | }; 19 | 20 | SC.Component.prototype.toObject = function () { 21 | // Return exportable object 22 | return { 23 | type: this.type, 24 | name: this.name, 25 | value: this.value, 26 | x: this.x, 27 | y: this.y, 28 | rotate: this.rotate, 29 | mirror: this.mirror, 30 | pinNet: this.pinNet 31 | }; 32 | }; 33 | 34 | SC.Component.prototype.fromObject = function (aObject) { 35 | // Set this from previously exported object 36 | this.type = aObject.type; 37 | this.name = aObject.name; 38 | this.value = aObject.value; 39 | this.x = aObject.x || 0; 40 | this.y = aObject.y || 0; 41 | this.rotate = aObject.rotate || 0; 42 | this.mirror = aObject.mirror || false; 43 | this.pinNet = aObject.pinNet || []; 44 | this.updatePins(); 45 | }; 46 | 47 | SC.Component.prototype.updatePins = function () { 48 | // Update pins after rotation or mirror 49 | var i, p = SC.specsPin[this.type], t, 50 | img = this.image(); 51 | this.pin = []; 52 | for (i = 0; i < p.length; i++) { 53 | t = SC.rotateAndMirror( 54 | this.rotate, 55 | this.mirror, 56 | p[i].x, 57 | p[i].y, 58 | img.width, 59 | img.height 60 | ); 61 | this.pin.push({ 62 | x: t.x, 63 | y: t.y, 64 | component: this, 65 | pin: p[i].name, 66 | index: i 67 | }); 68 | } 69 | }; 70 | 71 | SC.Component.prototype.pinXY = function (aPinIndex, aScreen) { 72 | // Get grid coordinates of given pin 73 | if (!this.pin[aPinIndex]) { 74 | console.warn('Component ' + this.type + ' ' + this.name + ' has no pin ' + aPinIndex); 75 | } 76 | var x = this.x + this.pin[aPinIndex].x, 77 | y = this.y + this.pin[aPinIndex].y, 78 | s; 79 | if (aScreen) { 80 | s = SC.v.canvasToScreen(x, y); 81 | return {x: s.x, y: s.y}; 82 | } 83 | return {x: x, y: y}; 84 | }; 85 | 86 | SC.Component.prototype.pinByName = function (aPinName) { 87 | // Get pin index of a pin by pin's name, e.g. 'E' of transistor_npn 88 | var i; 89 | for (i = 0; i < this.pin.length; i++) { 90 | if (this.pin[i].pin === aPinName) { 91 | return this.pin[i].index; 92 | } 93 | } 94 | }; 95 | 96 | SC.Component.prototype.image = function () { 97 | // Return correctly rotated and mirrored image of this component 98 | var t = this.type; 99 | if (this.type === 'capacitor' && this.value.toString().indexOf('u') >= 0) { 100 | t = 'capacitor_pol'; 101 | } 102 | if (!SC.image[t]) { 103 | console.warn('Image not found: ' + t); 104 | } 105 | return SC.image[t][this.rotate + (this.mirror ? 10 : 0)]; 106 | }; 107 | 108 | SC.Component.prototype.render = function (aContext) { 109 | // Render component 110 | var a = SC.v.canvasToScreen(this.x, this.y), 111 | i, 112 | p, 113 | img = this.image(); 114 | this.width = img.width / SC.gridSize; 115 | this.height = img.height / SC.gridSize; 116 | // image 117 | aContext.globalAlpha = 1; 118 | aContext.drawImage(img, a.x, a.y, this.width * SC.v.zoom, this.height * SC.v.zoom); 119 | // pins 120 | if (SC.show.net_numbers) { 121 | aContext.fillStyle = 'green'; 122 | aContext.textBaseline = 'middle'; 123 | aContext.textAlign = 'left'; 124 | aContext.font = '10px sans-serif'; 125 | for (i = 0; i < this.pin.length; i++) { 126 | p = this.pinXY(i, true); 127 | aContext.fillRect(p.x - 2, p.y - 2, 4, 4); 128 | aContext.fillText(this.pin[i].pin + ' p' + this.pin[i].index + ' n' + this.pinNet[i], p.x + 4, p.y); 129 | } 130 | } 131 | // selection 132 | if (SC.show.selection && this === SC.selected) { 133 | aContext.strokeStyle = 'red'; 134 | aContext.strokeRect(a.x, a.y, this.width * SC.v.zoom, this.height * SC.v.zoom); 135 | } 136 | aContext.fillStyle = 'black'; 137 | // name/value 138 | if (SC.renderLabel[this.type]) { 139 | SC.renderLabel[this.type](aContext, this); 140 | } else { 141 | SC.renderLabel.universal(aContext, this); 142 | } 143 | }; 144 | 145 | SC.Component.prototype.center = function () { 146 | // Get center point of component in grid coordinates 147 | var img = this.image(); 148 | return { 149 | x: this.x + img.width / 2 / SC.gridSize, 150 | y: this.y + img.height / 2 / SC.gridSize 151 | }; 152 | }; 153 | 154 | SC.Component.prototype.moveTo = function (aX, aY) { 155 | // Move component to X, Y (used only by netlist_random.js) 156 | this.x = aX; 157 | this.y = aY; 158 | return this; 159 | }; 160 | 161 | SC.Component.prototype.restoreFromComponents = function (aOldComponents) { 162 | // Restore position, mirror and flip from old components (after netlist reimport from stripboard) 163 | var i; 164 | if (aOldComponents && aOldComponents.item) { 165 | for (i = 0; i < aOldComponents.item.length; i++) { 166 | if (this.name === aOldComponents.item[i].name) { 167 | this.x = aOldComponents.item[i].x; 168 | this.y = aOldComponents.item[i].y; 169 | this.rotate = aOldComponents.item[i].rotate; 170 | this.mirror = aOldComponents.item[i].mirror; 171 | this.updatePins(); 172 | return true; 173 | } 174 | } 175 | } 176 | }; 177 | 178 | -------------------------------------------------------------------------------- /js/stripboard/component_connector_switch_dpdt.js: -------------------------------------------------------------------------------- 1 | // DPDT Switch connector 2 | "use strict"; 3 | // globals: document, window, CA 4 | 5 | var SC = window.SC || {}; 6 | 7 | SC.ConnectorSwitchDPDT = function (aName, aX1, aY1, aX2, aY2) { 8 | this.is_connector = true; 9 | this.type = 'connector_switch_dpdt'; 10 | this.name = aName; 11 | this.x1 = aX1 || 0; 12 | this.y1 = aY1 || 0; 13 | this.x2 = aX2 || 0; 14 | this.y2 = aY2 || 0; 15 | this.pin = 0; 16 | this.schematic = SC.ConnectorSwitchDPDT.recentSchematic || 'dpdt1'; 17 | }; 18 | 19 | SC.factory.connector_switch_dpdt = SC.ConnectorSwitchDPDT; 20 | SC.namingPrefix.connector_switch_dpdt = ''; 21 | 22 | SC.ConnectorSwitchDPDT.PIN_NAMES = { 23 | 0: 'Not Connected', 24 | 1: 'Pin 1 - Output A1', 25 | 2: 'Pin 2 - Input A', 26 | 3: 'Pin 3 - Output A2', 27 | 4: 'Pin 4 - Output B2', 28 | 5: 'Pin 5 - Input B', 29 | 6: 'Pin 6 - Output B1' 30 | }; 31 | 32 | SC.ConnectorSwitchDPDT.prototype.toObject = function () { 33 | // Return exportable object 34 | return { 35 | type: this.type, 36 | name: this.name, 37 | x1: this.x1, 38 | y1: this.y1, 39 | x2: this.x2, 40 | y2: this.y2, 41 | pin: this.pin, 42 | schematic: this.schematic 43 | }; 44 | }; 45 | 46 | SC.ConnectorSwitchDPDT.prototype.fromObject = function (aObject) { 47 | // Set this from previously exported object 48 | this.name = aObject.name; 49 | this.x1 = aObject.x1; 50 | this.y1 = aObject.y1; 51 | this.x2 = aObject.x2; 52 | this.y2 = aObject.y2; 53 | this.pin = aObject.pin; 54 | this.schematic = aObject.schematic; 55 | }; 56 | 57 | SC.ConnectorSwitchDPDT.prototype.toNetlist = function () { 58 | // Netlist of external SwitchDPDT are made by merging few ConnectorSwitchDPDT in netlist.js 59 | return; 60 | }; 61 | 62 | SC.ConnectorSwitchDPDT.prototype.nets = function () { 63 | // Return nets connected to pins 64 | return [ 65 | SC.grid.net(this.x1, this.y1) 66 | ]; 67 | }; 68 | 69 | SC.ConnectorSwitchDPDT.prototype.hasPin = function (aX, aY) { 70 | // Return true if given coords are at any of the pins, only start of connector is considered "real" 71 | return (this.x1 === aX) && (this.y1 === aY); 72 | }; 73 | 74 | SC.ConnectorSwitchDPDT.prototype.fixPinOrder = function (aDialog) { 75 | // Connector must start on board and end outside board, if it is other way around fix it 76 | return SC.components.fixPinOrder(this, aDialog); 77 | }; 78 | 79 | SC.ConnectorSwitchDPDT.prototype.distanceTo = function (aX, aY) { 80 | // Return distance to given point on grid 81 | return CA.distancePointLineSegment(aX, aY, this.x1, this.y1, this.x2, this.y2); 82 | }; 83 | 84 | SC.ConnectorSwitchDPDT.prototype.render = function (aContext) { 85 | // Render connector 86 | var a = SC.grid.gridToScreen(this.x1, this.y1, true), 87 | b = SC.grid.gridToScreen(this.x2, this.y2, true), 88 | line_width = 4 * SC.v.zoom; 89 | // name 90 | aContext.globalAlpha = 1; 91 | aContext.font = 'bold 12px sans-serif'; 92 | aContext.fillStyle = 'black'; 93 | aContext.lineWidth = 1; 94 | aContext.strokeStyle = 'white'; 95 | aContext.textBaseline = 'middle'; 96 | if (this.x1 === 0) { 97 | aContext.textAlign = 'right'; 98 | aContext.strokeText(this.name + ' (' + SC.ConnectorSwitchDPDT.PIN_NAMES[this.pin] + ')', b.x - line_width - (2 * line_width - 4), b.y); 99 | aContext.fillText(this.name + ' (' + SC.ConnectorSwitchDPDT.PIN_NAMES[this.pin] + ')', b.x - line_width - (2 * line_width - 4), b.y); 100 | } else { 101 | aContext.textAlign = 'left'; 102 | aContext.strokeText(this.name + ' (' + SC.ConnectorSwitchDPDT.PIN_NAMES[this.pin] + ')', b.x + line_width + (2 * line_width + 4), b.y); 103 | aContext.fillText(this.name + ' (' + SC.ConnectorSwitchDPDT.PIN_NAMES[this.pin] + ')', b.x + line_width + (2 * line_width + 4), b.y); 104 | } 105 | // line 106 | aContext.globalAlpha = 0.5; 107 | aContext.lineCap = 'round'; 108 | aContext.lineWidth = line_width; 109 | aContext.strokeStyle = 'black'; 110 | aContext.beginPath(); 111 | aContext.moveTo(a.x, a.y); 112 | aContext.lineTo(b.x, b.y); 113 | aContext.stroke(); 114 | aContext.globalAlpha = 1; 115 | }; 116 | 117 | SC.ConnectorSwitchDPDT.prototype.propertiesDialog = function (aCallback) { 118 | // Show properties dialog 119 | var t = this, on = this.name, d = SC.propertiesDialog(this, '', function (aButton) { 120 | // name must be set 121 | if (d.name.input.value === '') { 122 | alert('Name is required'); 123 | d.name.input.focus(); 124 | return false; 125 | } 126 | aCallback(aButton); 127 | if (aButton === 'Save') { 128 | t.pin = d.pin.input.value; 129 | t.schematic = d.schematic.input.value; 130 | SC.ConnectorSwitchDPDT.recentSchematic = t.schematic; 131 | // trickle down value to other pins 132 | SC.components.sameNameUpdateProperty(t.name, 'schematic', t.schematic); 133 | SC.components.sameNameUpdateProperty(on, 'name', t.name); 134 | } 135 | SC.render(); 136 | }); 137 | d.dlg.h1.textContent = 'Connector for DPDT switch'; 138 | 139 | d.schematic = CA.labelInput(d.dlg.content, 'Schematic', CA.select(['dpdt1', 'dpdt2'])); 140 | d.schematic.input.value = t.schematic; 141 | 142 | d.pin = CA.labelInput(d.dlg.content, 'Pin', CA.select(SC.ConnectorSwitchDPDT.PIN_NAMES)); 143 | d.pin.input.value = t.pin; 144 | 145 | d.dlg.content.appendChild(document.createTextNode('v1:')); 146 | 147 | d.img = document.createElement('img'); 148 | d.img.src = 'image/schematic/dpdt1.png'; 149 | d.img.style.marginLeft = '1ex'; 150 | d.img.style.marginRight = '2ex'; 151 | d.dlg.content.appendChild(d.img); 152 | 153 | d.dlg.content.appendChild(document.createTextNode('v2:')); 154 | 155 | d.img = document.createElement('img'); 156 | d.img.src = 'image/schematic/dpdt2.png'; 157 | d.img.style.marginLeft = '1ex'; 158 | d.dlg.content.appendChild(d.img); 159 | 160 | return d; 161 | }; 162 | 163 | -------------------------------------------------------------------------------- /schematic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |