Project Overview
April 16, 2023: This project will be updated in the coming weeks. There is an article on LinkedIn about this project, here: https://www.linkedin.com/pulse/demonstration-power-powershell-lance-fordham
This project shows how to convert an Ender 5 3D printer into a dual-use printer and laser etcher. The code and other files are offered without warranty or guarantee they will work for you. I also don’t offer guidance aside from what is presented here.
Electrical Conversion
The fan signal of the Ender 5 is used as the controlling signal for the laser. In my case, the fan signal is a 24 Vdc PWM signal, which needs to be converted to a 5 Vdc PWM signal. I do this with a simple resistor divider circuit. A PCB was created to facilitate connecting everything. You only need the resistors and the JST connectors. Get the PCB from OSHPark here: https://oshpark.com/shared_projects/3DCg3O8A
PowerShell Code
There are several different ways I could have written the code for generating the images and G-Code, but I wanted to challenge myself by doing everything with PowerShell. With this PowerShell code, I only needed to create images with rectangles and then convert them to G-Code. There is also a converter from G-Code back to an image to see if your G-Code is correct. You may not find the Designer useful if you need something other than rectangles. In that case, you can create your image outside of PowerShell if you make the image dimensions 830 x 830 pixels. The ImageToGCode and GCodeToImage modules should still work for you.
Save the files as named, put them into the same directory, and run the ImageToGCodeForm.ps1 file for the GUI.
Using Module ".\ImageToGCode.psm1";
Using Module ".\GCodeToImage.psm1";
Using Module ".\MouseToImage.psm1";
$DebugPreference = "Continue";
$VerbosePreference = "Continue";
Set-Location $PSScriptRoot;
enum ButtonNames{
BUTTON1 = 1;
BUTTON2 = 2;
BUTTON3 = 3;
BUTTON4 = 4;
BUTTON5 = 5;
}
enum RadioButtonNames{
RADIO1 = 1;
RADIO2 = 2;
RADIO3 = 3;
}
class ImageToGCodeForm{
#Paths
[String] $basepath = $PSScriptRoot;
[String] $canvaspath = "$($this.basepath)\canvas.bmp";
[String] $inputpath = "$($this.basepath)\canvas.bmp";
[String] $outputpath = $null;
#Form Elements
[System.Windows.Forms.Form] $applicationform;
[System.Windows.Forms.TabControl] $tabcontrol;
[System.Windows.Forms.Tabpage[]] $tabs;
[System.Windows.Forms.TextBox[]] $tab_textboxes;
[System.Windows.Forms.TextBox[]] $form_textboxes;
[System.Windows.Forms.Button[]] $form_buttons;
[System.Windows.Forms.Button[]] $tab_buttons;
[System.Windows.Forms.RadioButton[]] $tab_radiobuttons;
[System.Windows.Forms.RadioButton[]] $form_radiobuttons;
[System.Windows.Forms.GroupBox[]] $form_groupboxes;
[System.Windows.Forms.ProgressBar[]] $form_progressbars;
[System.Windows.Forms.PictureBox[]] $tab_pictureboxes;
[System.Windows.Forms.Label[]] $form_labels;
#Styling
[System.Drawing.Font] $font = [System.Drawing.Font]::new("Tahoma",12,[System.Drawing.FontStyle]::Regular);
[System.Drawing.Size] $buttonsize = [System.Drawing.Size]::new(250,25);
[System.Drawing.Color] $backcolor = [System.Drawing.Color]::BlanchedAlmond;
[ImageToGCode] $image2gcode;
[GCodeToImage] $gcodetoimage;
[MouseToImage] $mousetoimage;
[int] $progressStep = 0;
ImageToGCodeForm(){
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing");
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms");
$this.start();
}
ImageToGCodeForm([String] $inputpath, [String] $outputpath){
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing");
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms");
$this.inputpath = $inputpath;
$this.outputpath = $outputpath;
$this.start();
}
start(){
$this.instantiateObjects();
$this.setupApplicationForm();
$this.setupTabControl();
$this.setupTabs();
$this.setupTextboxes();
$this.setupButtons();
$this.setupRadioButtons();
$this.setupProgressBar();
$this.setupPictureBoxes();
$this.setupLabels();
$this.showForm();
$this.millerTime();
}
millerTime(){
if($this.tab_pictureboxes -ne $null){
$this.tab_pictureboxes.ForEach({
if($_.BackgroundImage -ne $null){
$_.BackgroundImage.Dispose();
}
});
}
if($this.image2gcode -ne $null){
if($this.image2gcode.image.file -ne $null){
$this.image2gcode.image.file.Dispose();
}
}
$this.applicationform.Dispose();
}
setupApplicationForm(){
$thisForm = $this;
$this.applicationform = [System.Windows.Forms.Form]::new();
$this.applicationform.Name = "MainForm";
$this.applicationform.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen;
$this.applicationform.Topmost = $true;
$this.applicationform.Size = [System.Drawing.Size]::new(1920,1080);
$this.applicationform.Text = "ImageToGCode";
$this.applicationform.Font = $this.font;
$this.applicationform.AutoScale = $true;
}
setupTabControl(){
$this.tabcontrol = [System.Windows.Forms.TabControl]::new();
$this.tabcontrol.Size = [System.Drawing.Size]::new(1880,850);
$this.tabcontrol.Location = [System.Drawing.Point]::new(15,60);
$this.tabcontrol.Font = $this.font;
$this.applicationform.Controls.Add($this.tabcontrol);
}
setupTabs(){
$this.addTabs(2,$this.tabcontrol.Controls);
$this.tabs[0].Name = "Tab1";
$this.tabs[0].Text = "Images";
$this.tabs[0].DataBindings.DefaultDataSourceUpdateMode = 0;
$this.tabs[0].UseVisualStyleBackColor = $True;
$this.tabs[0].BackColor = $this.backcolor;
$this.tabs[0].Font = $this.font;
$this.tabs[1].Name = "Tab2";
$this.tabs[1].Text = "Settings";
$this.tabs[1].DataBindings.DefaultDataSourceUpdateMode = 0;
$this.tabs[1].UseVisualStyleBackColor = $True;
$this.tabs[1].BackColor = $this.backcolor;
$this.tabs[1].Font = $this.font;
}
setupTextboxes(){
<#
$this.addTextBoxes(1,$this.tabs[0].Controls);
$this.addTextBoxes(1,$this.tabs[1].Controls);
$this.tab_textboxes[0].Name = "Textbox1";
$this.tab_textboxes[0].Text = "Hello";
$this.tab_textboxes[0].Size = [System.Drawing.Size]::new(200,50);
$this.tab_textboxes[0].Location = [System.Drawing.Point]::new(15,0);
$this.tab_textboxes[0].Multiline = $false;
$this.tab_textboxes[0].Font = $this.font;
$this.tab_textboxes[0].ReadOnly = $false;
$this.tab_textboxes[1].Name = "Textbox2";
$this.tab_textboxes[1].Text = "World";
$this.tab_textboxes[1].Size = [System.Drawing.Size]::new(200,400);
$this.tab_textboxes[1].Location = [System.Drawing.Point]::new(15,0);
$this.tab_textboxes[1].Multiline = $true;
$this.tab_textboxes[1].Font = $this.font;
$this.tab_textboxes[1].ReadOnly = $false;
$this.addTextBoxes(2);
$this.form_textboxes[0].Name = "TextBox3";
$this.form_textboxes[0].Text = "Hello";
$this.form_textboxes[0].Size = [System.Drawing.Size]::new(200,50);
$this.form_textboxes[0].Location = [System.Drawing.Point]::new(15,0);
$this.form_textboxes[0].Multiline = $false;
$this.form_textboxes[0].Font = $this.font;
$this.form_textboxes[0].ReadOnly = $false;
$this.form_textboxes[1].Name = "TextBox4";
$this.form_textboxes[1].Text = "World";
$this.form_textboxes[1].Size = [System.Drawing.Size]::new(200,50);
$this.form_textboxes[1].Location = [System.Drawing.Point]::new(300,0);
$this.form_textboxes[1].Multiline = $false;
$this.form_textboxes[1].Font = $this.font;
$this.form_textboxes[1].ReadOnly = $false;
#>
}
setupButtons(){
$this.addButtons(5);
$thisForm = $this;
$this.form_buttons[0].Name = [ButtonNames]::BUTTON1;
$this.form_buttons[0].Text = "Designer";
$this.form_buttons[0].Size = $this.buttonsize;
$this.form_buttons[0].Location = [System.Drawing.Point]::new(15,30);
$this.form_buttons[0].Font = $this.font;
$this.form_buttons[0].Add_Click({ param($sender, $eventArgs) $thisForm.eventHandler($sender, $eventArgs) }.GetNewClosure() );
$this.form_buttons[1].Name = [ButtonNames]::BUTTON2;
$this.form_buttons[1].Text = "Open Image";
$this.form_buttons[1].Size = $this.buttonsize;
$this.form_buttons[1].Location = [System.Drawing.Point]::new(($this.buttonsize.Width + 10),30);
$this.form_buttons[1].Font = $this.font;
$this.form_buttons[1].Add_Click({ param($sender, $eventArgs) $thisForm.eventHandler($sender, $eventArgs) }.GetNewClosure() );
$this.form_buttons[2].Name = [ButtonNames]::BUTTON3;
$this.form_buttons[2].Text = "Image to GCode";
$this.form_buttons[2].Size = $this.buttonsize;
$this.form_buttons[2].Location = [System.Drawing.Point]::new(($this.buttonsize.Width * 2 + 5),30);
$this.form_buttons[2].Font = $this.font;
$this.form_buttons[2].Add_Click({ param($sender, $eventArgs) $thisForm.eventHandler($sender, $eventArgs) }.GetNewClosure() );
$this.form_buttons[3].Name = [ButtonNames]::BUTTON4;
$this.form_buttons[3].Text = "GCode to Image";
$this.form_buttons[3].Size = $this.buttonsize;
$this.form_buttons[3].Location = [System.Drawing.Point]::new(($this.buttonsize.Width * 3 + 5),30);
$this.form_buttons[3].Font = $this.font;
$this.form_buttons[3].Add_Click({ param($sender, $eventArgs) $thisForm.eventHandler($sender, $eventArgs) }.GetNewClosure() );
$this.form_buttons[4].Name = [ButtonNames]::BUTTON5;
$this.form_buttons[4].Text = "Change Output";
$this.form_buttons[4].Size = $this.buttonsize;
$this.form_buttons[4].Location = [System.Drawing.Point]::new(15,910);
$this.form_buttons[4].Font = $this.font;
$this.form_buttons[4].Add_Click({ param($sender, $eventArgs) $thisForm.eventHandler($sender, $eventArgs) }.GetNewClosure() );
}
setupRadioButtons(){
<#
$thisForm = $this;
$this.form_groupboxes += [System.Windows.Forms.GroupBox]::new();
$this.form_groupboxes[0].Size = [System.Drawing.Size]::new(400,50);
$this.form_groupboxes[0].Location = [System.Drawing.Point]::new(300,50);
$this.applicationform.Controls.Add($this.form_groupboxes[$this.form_groupboxes.Count - 1]);
$this.addRadioButtons(1,$this.form_groupboxes[0].Controls);
$this.form_radiobuttons[0].Name = [RadioButtonNames]::RADIO1;
$this.form_radiobuttons[0].Text = "Grayscale";
$this.form_radiobuttons[0].Location = [System.Drawing.Point]::new(15,20);
$this.form_radiobuttons[0].ForeColor = $this.backcolor;
$this.form_radiobuttons[0].Checked = $false;
$this.form_radiobuttons[0].Add_Click({ param($sender, $eventArgs) $thisForm.eventHandler($sender, $eventArgs) }.GetNewClosure() );
$this.form_radiobuttons[0].Font = $this.font;
#>
}
setupProgressBar(){
# Progress Bar
$this.form_progressbars += [System.Windows.Forms.ProgressBar]::new();
$this.applicationform.Controls.Add($this.form_progressbars[$this.form_progressbars.Count - 1]);
$this.form_progressbars[0].Location = [System.Drawing.Point]::new(15,950);
$this.form_progressbars[0].Maximum = 1000
$this.form_progressbars[0].Minimum = 0
$this.form_progressbars[0].Size = [System.Drawing.Size]::new(1860,20);
$this.form_progressbars[0].Step = 1
$this.form_progressbars[0].Style = [System.Windows.Forms.ProgressBarStyle]::Continuous;
$this.form_progressbars[0].BackColor = [System.Drawing.Color]::GreenYellow;
$this.form_progressbars[0].Hide();
}
setupPictureBoxes(){
if(!(Test-Path $this.canvaspath)){
$this.generateCanvas();
}
#Input PictureBox
$this.tab_pictureboxes += [System.Windows.Forms.PictureBox]::new();
$this.tab_pictureboxes[0].Location = [System.Drawing.Point]::new(15,15);
$this.tab_pictureboxes[0].ClientSize = [System.Drawing.Size]::new(831,831);
$this.tab_pictureboxes[0].BackColor = [System.Drawing.Color]::CadetBlue;
$this.tab_pictureboxes[0].SizeMode = [System.Windows.Forms.PictureBoxSizeMode]::Zoom;
$this.tab_pictureboxes[0].BackgroundImageLayout = [System.Windows.Forms.ImageLayout]::Center;
if(Test-Path $this.inputpath){
$this.tab_pictureboxes[0].BackgroundImage = [System.Drawing.Bitmap]::FromFile($this.inputpath);
}else{
$this.generateCanvas();
}
$this.tabs[0].Controls.Add($this.tab_pictureboxes[0]);
#Output PictureBox
$this.tab_pictureboxes += [System.Windows.Forms.PictureBox]::new();
$this.tab_pictureboxes[1].Location = [System.Drawing.Point]::new(980,15);
$this.tab_pictureboxes[1].ClientSize = [System.Drawing.Size]::new(831,831);
$this.tab_pictureboxes[1].BackColor = [System.Drawing.Color]::CadetBlue;
$this.tab_pictureboxes[1].SizeMode = [System.Windows.Forms.PictureBoxSizeMode]::Zoom;
$this.tab_pictureboxes[1].BackgroundImageLayout = [System.Windows.Forms.ImageLayout]::Center;
if($this.outputpath){
$this.tab_pictureboxes[1].BackgroundImage = [System.Drawing.Bitmap]::FromFile($this.outputpath);
}else{
$this.generateCanvas();
}
$this.tabs[0].Controls.Add($this.tab_pictureboxes[1]);
}
setupLabels(){
$this.form_labels += [System.Windows.Forms.Label]::new();
$this.form_labels[0].Location = [System.Drawing.Point]::new(275,912);
$this.form_labels[0].Size = [System.Drawing.Size]::new(920,20);
$this.form_labels[0].ForeColor = [System.Drawing.Color]::DarkBlue;
$this.form_labels[0].Text = "Output saved to $($this.outputpath)";
$this.applicationform.Controls.Add($this.form_labels[0]);
}
addTabs([int] $qty, [System.Windows.Forms.TabControl+ControlCollection] $attachto){
1..$qty | %{
$this.tabs += [System.Windows.Forms.Tabpage]::new();
$attachto.Add($this.tabs[$this.tabs.Count - 1]);
}
}
addTextBoxes([int] $qty, [System.Windows.Forms.TabPage+TabPageControlCollection] $attachto){
1..$qty | %{
$this.tab_textboxes += [System.Windows.Forms.TextBox]::new();
$attachto.Add($this.tab_textboxes[$this.tab_textboxes.Count - 1]);
}
}
addTextBoxes([int] $qty){
1..$qty | %{
$this.form_textboxes += [System.Windows.Forms.TextBox]::new();
$this.applicationform.Controls.Add($this.form_textboxes[$this.form_textboxes.Count - 1]);
}
}
addButtons([int] $qty,[System.Windows.Forms.TabPage+TabPageControlCollection] $attachto){
1..$qty | %{
$this.tab_buttons += [System.Windows.Forms.Button]::new();
$attachto.Add($this.tab_buttons[$this.tab_buttons.Count - 1]);
}
}
addButtons([int] $qty){
1..$qty | %{
$this.form_buttons += [System.Windows.Forms.Button]::new();
$this.applicationform.Controls.Add($this.form_buttons[$this.form_buttons.Count - 1]);
}
}
addRadioButtons([int] $qty,[System.Windows.Forms.TabPage+TabPageControlCollection] $attachto){
1..$qty | %{
$this.tab_radiobuttons += [System.Windows.Forms.RadioButton]::new();
$attachto.Add($this.tab_radiobuttons[$this.tab_radiobuttons.Count - 1]);
}
}
addRadioButtons([int] $qty, $attachto){
1..$qty | %{
$this.form_radiobuttons += [System.Windows.Forms.RadioButton]::new();
$attachto.Add($this.form_radiobuttons[$this.form_radiobuttons.Count - 1]);
}
}
openDesigner(){
Write-Debug "Opening designer";
$this.mousetoimage = [MouseToImage]::new();
$this.mousetoimage.start();
$this.tab_pictureboxes[0].BackgroundImage = [System.Drawing.Bitmap]::FromFile($this.mousetoimage.outputpath);
$this.inputpath = $this.mousetoimage.outputpath;
if($this.image2gcode -ne $null){
$this.image2gcode.inputpath = $this.inputpath;
}
}
getInputPath(){
if($this.image2gcode.selectFile()){
Write-Debug "Path: $($this.image2gcode.image.path)";
$this.inputpath = "$($this.image2gcode.image.path)\$($this.image2gcode.image.filename)";
$this.tab_pictureboxes[0].BackgroundImage = $this.image2gcode.image.file;
}
}
getOutputPath(){
if($this.image2gcode.image.path -eq $null){
if($this.image2gcode.selectFile()){
Write-Debug "Path: $($this.image2gcode.image.path)";
}
}
$this.image2gcode.outputpath = "$($this.image2gcode.image.path)\$($this.image2gcode.image.filenamenoext).gcode";
$this.outputpath = "$($this.image2gcode.image.path)\$($this.image2gcode.image.filenamenoext).bmp";
$this.gcodetoimage.outputpath = $this.outputpath;
$this.mousetoimage.outputpath = $this.outputpath;
$this.form_labels[0].Text = "Output saved to $($this.outputpath)";
}
scanImage(){
$this.form_progressbars[0].Show();
if($this.image2gcode.processsettings.scandirection -eq [ScanDirection]::X){
Write-Verbose "Scanning direction is X";
$this.form_progressbars[0].Maximum = $this.image2gcode.image.file.Height - 1;
for($y = 0; $y -lt $this.image2gcode.image.file.Height; $y++){
$this.form_progressbars[0].PerformStep();
Write-Host "Row $($y) of $($this.image2gcode.image.file.Height)";
for($x = 0; $x -lt $this.image2gcode.image.file.Width; $x++){
$this.image2gcode.processPixel($x, $y);
}
$this.image2gcode.processsettings.scanlines += 1;
}
}else{
Write-Verbose "Scanning direction is Y";
$this.form_progressbars[0].Maximum = $this.image2gcode.image.file.Width - 1;
for($x = 0; $x -lt $this.image2gcode.image.file.Width; $x++){
$this.form_progressbars[0].PerformStep();
Write-Host "Row $($x) of $($this.image2gcode.image.file.Width)";
for($y = 0; $y -lt $this.image2gcode.image.file.Height; $y++){
$this.image2gcode.processPixel($x, $y);
}
$this.image2gcode.processsettings.scanlines += 1;
}
}
$this.form_progressbars[0].Value = 0;
$this.form_progressbars[0].Hide();
}
runImageToGCode(){
$this.image2gcode.getImageMetadata();
Write-Debug "Done with image metadata";
$this.image2gcode.initializeOffsets();
Write-Debug "Done with initialize offsets";
$this.image2gcode.initializeGCode();
Write-Debug "Done with initialize gcode";
$this.scanImage();
Write-Debug "Done scanning image";
$this.image2gcode.finalizeGCode();
Write-Debug "Done finalizing GCode";
$this.image2gcode.millerTime();
Write-Debug "Miller time!";
}
runGCodeToImage(){
if(!$this.gcodetoimage.inputpath){
$this.gcodetoimage.getInputFile();
}
$this.gcodetoimage.getDPI();
$this.gcodetoimage.getScreenScaleFactor();
$this.gcodetoimage.dpmm = ($this.gcodetoimage.dpi * $this.gcodetoimage.screenscalefactor) / 25.4;
Write-Verbose "dpi: $($this.gcodetoimage.dpi)";
Write-Verbose "dpmm: $($this.gcodetoimage.dpmm)";
$this.gcodetoimage.bedx = [Math]::Round((220 * $this.gcodetoimage.dpmm)) + 1;
$this.gcodetoimage.bedy = [Math]::Round((220 * $this.gcodetoimage.dpmm)) + 1;
Write-Verbose "bedx: $($this.gcodetoimage.bedx)";
Write-Verbose "bedy: $($this.gcodetoimage.bedy)";
$this.gcodetoimage.bitmap = [System.Drawing.Bitmap]::new($this.gcodetoimage.bedx, $this.gcodetoimage.bedy);
$this.gcodetoimage.graphics = [System.Drawing.Graphics]::FromImage($this.gcodetoimage.bitmap);
$this.gcodetoimage.graphics.Clear([System.Drawing.Color]::White);
$this.gcodetoimage.regex = [Regex]::new($this.gcodetoimage.filter);
$this.gcodetoimage.getGCodeFromFile();
Write-Host $this.gcodetoimage.gcode;
$this.parseGCode();
$this.gcodetoimage.saveFile();
$this.gcodetoimage.millerTime();
$this.tab_pictureboxes[1].BackgroundImage = [System.Drawing.Bitmap]::FromFile($this.gcodetoimage.outputpath);
}
parseGCode(){
$this.form_progressbars[0].Show();
$this.gcodetoimage.matches = $this.gcodetoimage.regex.Matches($this.gcodetoimage.gcode);
Write-Debug "Matches: $($this.gcodetoimage.matches)";
$this.form_progressbars[0].Maximum = $this.gcodetoimage.matches.Count - 1;
Write-Verbose "Progress maximum: $($this.form_progressbars[0].Maximum)";
for($i = 0; $i -lt $this.gcodetoimage.matches.Count; $i+=2){
$this.form_progressbars[0].PerformStep();
$this.gcodetoimage.xpixel = (([int]([String] $this.gcodetoimage.matches[$i]).Replace("X",""))) * $this.gcodetoimage.dpmm;
$this.gcodetoimage.ypixel = (([int]([String] $this.gcodetoimage.matches[$i + 1]).Replace("Y",""))) * $this.gcodetoimage.dpmm;
if(($this.gcodetoimage.xpixel -gt $this.gcodetoimage.bedx) -or ($this.gcodetoimage.xpixel -lt 0)){
$this.gcodetoimage.xpixel = $this.gcodetoimage.bedx;
}
if(($this.gcodetoimage.ypixel -gt $this.gcodetoimage.bedy) -or ($this.gcodetoimage.ypixel -lt 0)){
$this.gcodetoimage.ypixel = $this.gcodetoimage.bedy;
}
Write-Verbose "x: $($this.gcodetoimage.xpixel)";
Write-Verbose "y: $($this.gcodetoimage.ypixel)";
$this.gcodetoimage.bitmap.SetPixel($this.gcodetoimage.xPixel, $this.gcodetoimage.yPixel, [System.Drawing.Color]::FromArgb($this.gcodetoimage.red, $this.gcodetoimage.green, $this.gcodetoimage.blue));
}
$this.form_progressbars[0].Value = 0;
$this.form_progressbars[0].Hide();
}
generateCanvas(){
if(!(Test-Path $this.canvaspath)){
$this.inputpath = $this.canvaspath;
[System.Drawing.SolidBrush] $bisquebrush = [System.Drawing.SolidBrush]::new([System.Drawing.Color]::Bisque);
[System.Drawing.Pen] $bisquepen = [System.Drawing.Pen]::new([System.Drawing.Color]::Bisque);
[System.Drawing.Bitmap] $bmp = [System.Drawing.Bitmap]::new(831, 831);
[System.Drawing.Graphics] $g = [System.Drawing.Graphics]::FromImage($bmp);
[System.Drawing.Rectangle] $rect = [System.Drawing.Rectangle]::new(0,0,$bmp.Width,$bmp.Height);
$g.FillRectangles($bisquebrush, $rect);
$g.DrawRectangle($bisquepen, $rect);
$bmp.Save($this.canvaspath, [System.Drawing.Imaging.ImageFormat]::Bmp);
$g.Dispose();
$bmp.Dispose();
}
}
instantiateObjects(){
if($this.image2gcode -eq $null){
$this.image2gcode = [ImageToGCode]::new();
}
if($this.gcodetoimage -eq $null){
$this.gcodetoimage = [GCodeToImage]::new();
}
if($this.mousetoimage -eq $null){
$this.mousetoimage = [MouseToImage]::new();
}
}
eventHandler([object] $sender, [System.EventArgs] $eventArgs) {
if($sender){
Write-Debug ($sender.Name);
$args = ($eventArgs | ConvertTo-Json -Depth 100);
switch($sender.Name){
([ButtonNames]::BUTTON1){
#$this.getOutputPath();
$this.openDesigner();
}
([ButtonNames]::BUTTON2){
$this.getInputPath();
}
([ButtonNames]::BUTTON3){
$this.getOutputPath();
$this.runImageToGCode();
}
([ButtonNames]::BUTTON4){
$this.runGCodeToImage();
}
([ButtonNames]::BUTTON5){
$this.getOutputPath();
$this.form_labels[0].Text = "Output saved to $($this.outputpath)";
}
([RadioButtonNames]::RADIO1){
[System.Windows.Forms.MessageBox]::Show("Hello $($sender.Name).`n`n$($args)" , $sender.Name);
}
("MainFormClosing"){
$this.MainForm_FormClosing($sender,$eventArgs);
}
default{
[System.Windows.Forms.MessageBox]::Show("Hello $($sender.Name).`n`n$($args)" , "Default");
}
}
}
}
showForm(){
[System.Windows.Forms.Application]::Run($this.applicationform);
#[void] $this.applicationform.ShowDialog();
}
}
$itgcf = [ImageToGCodeForm]::new();
Set-Location $PSScriptRoot;
class MouseToImage{
[String] $basepath = $PSScriptRoot;
[String] $canvaspath = "$($this.basepath)\canvas.bmp";
[String] $inputpath = "$($this.basepath)\canvas.bmp";
[String] $outputpath = $null;
[System.Windows.Forms.Form] $applicationform;
[System.Drawing.Point] $lastPoint = [System.Drawing.Point]::Empty;
[System.Drawing.Point] $startPoint = [System.Drawing.Point]::Empty;
[bool] $isMouseDown = $false;
[System.Windows.Forms.PictureBox] $pictureBox1;
[System.Drawing.Rectangle[]] $rectangles;
[System.Drawing.Font] $font = [System.Drawing.Font]::new("Tahoma",8,[System.Drawing.FontStyle]::Regular);
[System.Windows.Forms.Button[]] $form_buttons;
[bool] $drawlines = $true;
[int] $dpi = 96;
[int] $numcellsx = 220;
[int] $numcellsy = 220;
[int] $laseroffsetx = -12;
[int] $laseroffsety = 55;
[double] $dpmm;
[int] $cellsize;
[int] $cellcount = 10;
[double] $screenscalefactor = 1;
[System.Drawing.Pen] $blackpen = [System.Drawing.Pen]::new([System.Drawing.Color]::Black);
[System.Drawing.Pen] $whitepen = [System.Drawing.Pen]::new([System.Drawing.Color]::White);
[System.Drawing.Pen] $redpen = [System.Drawing.Pen]::new([System.Drawing.Color]::Red);
[System.Drawing.SolidBrush] $blackbrush = [System.Drawing.SolidBrush]::new([System.Drawing.Color]::Black);
[System.Drawing.SolidBrush] $whitebrush = [System.Drawing.SolidBrush]::new([System.Drawing.Color]::White);
MouseToImage($path){
$this.inputpath = $path;
$this.start();
}
MouseToImage(){
$this.inputpath = $this.canvaspath;
}
start(){
$thisForm = $this;
$this.getScreenScaleFactor();
#$this.dpi = $this.getDPI();
Write-Debug "This.dpi: $($this.dpi)";
Write-Debug "This screenscalefactor: $($this.screenscalefactor)";
$this.dpmm = ($this.dpi * $this.screenscalefactor) / 25.4;
Write-Debug "This.dpmm: $($this.dpmm)";
$this.cellsize = $this.round($this.cellcount * $this.dpmm);
Write-Debug "cellsize: $($this.cellsize)";
$this.applicationform = [System.Windows.Forms.Form]::new();
$this.applicationform.Name = "MainForm";
$this.applicationform.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen;
$this.applicationform.Topmost = $true;
$this.applicationform.Size = [System.Drawing.Size]::new(1000,1000);
$this.applicationform.Text = "ImageToGCode";
$this.applicationform.AutoScale = $true;
$this.applicationform.Add_FormClosing({ param($sender, $FormClosingEventArgs) $thisForm.MainForm_FormClosing(@{Name = "MainFormClosing"}, $FormClosingEventArgs) }.GetNewClosure());
$this.pictureBox1 = [System.Windows.Forms.PictureBox]::new();
$this.pictureBox1.Location = [System.Drawing.Point]::new(15,15);
$this.pictureBox1.ClientSize = [System.Drawing.Size]::new([Math]::Round(($this.numcellsx * $this.dpmm)),[Math]::Round(($this.numcellsy * $this.dpmm)));
$this.pictureBox1.BackColor = [System.Drawing.Color]::CadetBlue;
$this.pictureBox1.SizeMode = [System.Windows.Forms.PictureBoxSizeMode]::Zoom;
$this.pictureBox1.BackgroundImageLayout = [System.Windows.Forms.ImageLayout]::Center;
if(!(Test-Path $this.inputpath)){
$this.generateBitmap();
}
$this.pictureBox1.BackgroundImage = [System.Drawing.Bitmap]::FromFile($this.inputpath);
#$this.pictureBox1.ClientSize = $this.pictureBox1.BackgroundImage.Size;
$this.pictureBox1.Add_Click({ param($sender, $eventArgs) $thisForm.clearButton_Click(@{Name = "Picturebox1Click"}, $eventArgs) }.GetNewClosure() );
$this.pictureBox1.Add_MouseDown({ param($sender, $eventArgs) $thisForm.pictureBox1_MouseDown(@{Name="PictureBox0_MouseDown"}, $eventArgs) }.GetNewClosure() );
$this.pictureBox1.Add_MouseUp({ param($sender, $eventArgs) $thisForm.pictureBox1_MouseUp(@{Name="PictureBox0_MouseUp"}, $eventArgs) }.GetNewClosure() );
$this.pictureBox1.Add_MouseMove({ param($sender, $eventArgs) $thisForm.pictureBox1_MouseMove(@{Name="PictureBox0_MouseMove"}, $eventArgs) }.GetNewClosure() );
$this.pictureBox1.Add_Paint({ param($sender, $PaintEventArgs) $thisForm.pictureBox1_Paint(@{Name="PictureBox0_Paint"}, $PaintEventArgs) }.GetNewClosure() );
$this.applicationform.Controls.Add($this.pictureBox1);
$this.form_buttons += [System.Windows.Forms.Button]::new();
$this.applicationform.Controls.Add($this.form_buttons[$this.form_buttons.Count - 1]);
$this.form_buttons[0].Name = "clearButton";
$this.form_buttons[0].Text = "Clear";
$this.form_buttons[0].Size = [System.Drawing.Size]::new(100,25);
$this.form_buttons[0].Location = [System.Drawing.Point]::new(30,900);
$this.form_buttons[0].Font = $this.font;
$this.form_buttons[0].Add_Click({ param($sender, $eventArgs) $thisForm.clearButton_Click(@{Name = "clearButton"}, $eventArgs) }.GetNewClosure() );
$this.form_buttons += [System.Windows.Forms.Button]::new();
$this.applicationform.Controls.Add($this.form_buttons[$this.form_buttons.Count - 1]);
$this.form_buttons[1].Name = "saveButton";
$this.form_buttons[1].Text = "Save";
$this.form_buttons[1].Size = [System.Drawing.Size]::new(100,25);
$this.form_buttons[1].Location = [System.Drawing.Point]::new(130,900);
$this.form_buttons[1].Font = $this.font;
$this.form_buttons[1].Add_Click({ param($sender, $eventArgs) $thisForm.Save_Click(@{Name = "saveButton"}, $eventArgs) }.GetNewClosure() );
$this.showForm();
}
showForm(){
#[System.Windows.Forms.Application]::Run($this.applicationform);
[void] $this.applicationform.ShowDialog();
$this.applicationform.Invalidate();
}
millerTime(){
if($this.pictureBox1 -ne $null){
#Write-Debug "Disposing";
$this.pictureBox1.BackgroundImage.Dispose();
}
$this.applicationform.Dispose();
$this.applicationform.Dispose();
}
generateBitmap(){
$this.inputpath = $this.canvaspath;
[System.Drawing.Bitmap] $bmp = [System.Drawing.Bitmap]::new($this.pictureBox1.Width, $this.pictureBox1.Height);
[System.Drawing.Graphics] $g = [System.Drawing.Graphics]::FromImage($bmp);
[System.Drawing.Rectangle] $rect = [System.Drawing.Rectangle]::new(0,0,$bmp.Width,$bmp.Height);
$g.FillRectangles($this.whitebrush, $rect);
$g.DrawRectangle($this.whitepen, $rect);
$bmp.Save($this.canvaspath, [System.Drawing.Imaging.ImageFormat]::Bmp);
$g.Dispose();
$bmp.Dispose();
}
Save_Click([Object] $sender, [System.Windows.Forms.MouseEventArgs] $m ){
Write-Debug "Saving";
if($sender.Name -eq "saveButton"){
#$this.drawlines = $false;
[System.Drawing.Bitmap] $bmp = [System.Drawing.Bitmap]::new($this.pictureBox1.Width, $this.pictureBox1.Height);
[System.Drawing.Graphics] $g = [System.Drawing.Graphics]::FromImage($bmp);
[System.Drawing.Rectangle] $rect = [System.Drawing.Rectangle]::new(0,0,$bmp.Width,$bmp.Height);
$g.FillRectangles($this.whitebrush, $rect);
$g.DrawRectangle($this.whitepen, $rect);
if($this.rectangles.Count -gt 0){
$g.FillRectangles($this.blackbrush, $this.rectangles);
$g.DrawRectangles($this.blackpen, $this.rectangles);
}
if(!$this.outputpath){
$this.getOutputFile();
}
Write-Debug "Output Path: $($this.outputpath)";
$bmp.Save($this.outputpath, [System.Drawing.Imaging.ImageFormat]::Bmp);
$g.Dispose();
$bmp.Dispose();
$this.drawlines = $true;
$this.millerTime();
}
}
getOutputFile(){
$FileBrowser = New-Object System.Windows.Forms.SaveFileDialog -Property @{
InitialDirectory = [Environment]::GetFolderPath('MyComputer');
Filter = 'Image Files (*.jpg,*.jpeg,*.png,*.tif,*.tiff,*.bmp)|*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp';
CheckFileExists = $false;
CheckPathExists = $false;
ValidateNames = $false;
}
$null = $FileBrowser.ShowDialog();
if($($FileBrowser.FileName)){
$this.outputpath = $FileBrowser.FileName;
}else{
Write-Host "No file selected. Cancelling.";
break;
}
}
[System.Drawing.Rectangle] getNewRectangle(){
Write-Debug "New Rectangle";
return [System.Drawing.Rectangle]::new(
[Math]::Min($this.lastPoint.X, $this.startPoint.X),
[Math]::Min($this.lastPoint.Y, $this.startPoint.Y),
[Math]::Abs($this.lastPoint.X - $this.startPoint.X),
[Math]::Abs($this.lastPoint.Y - $this.startPoint.Y));
}
[double] round([double] $pre){
return ([Math]::Round($pre,2));
}
pictureBox1_Paint([object] $sender, [System.Windows.Forms.PaintEventArgs] $e){
[System.Drawing.Graphics] $g = $e.Graphics;
if($this.drawlines){
for ([int] $y = 0; $y -lt $this.numcellsy; ++$y){
$g.DrawLine($this.blackpen, 0, $this.round($y * $this.cellsize), $this.round($this.numcellsy * $this.cellcount), $this.round($y * $this.cellsize));
$g.DrawString(($this.round(($y * $this.cellsize) / $this.dpmm)).ToString(),$this.font,[System.Drawing.SolidBrush]::new([System.Drawing.Color]::Black),0,$this.round($y * $this.cellsize));
}
for ([int] $x = 0; $x -lt $this.numcellsx; ++$x){
$g.DrawLine($this.blackpen, $x * $this.cellsize, 0, $x * $this.cellsize, $this.numcellsx * $this.cellcount);
$g.DrawString(($this.round(($x * $this.cellsize) / $this.dpmm)).ToString(),$this.font,[System.Drawing.SolidBrush]::new([System.Drawing.Color]::Black),$this.round($x * $this.cellsize),0);
}
if($this.laseroffsetx -lt 0){
$xmax = [Math]::Round(($this.numcellsx + $this.laseroffsetx) * $this.dpmm);
#Write-Debug "xmax: $($xmax) ";
$g.DrawLine($this.redpen, $this.round($xmax), 0, $this.round($xmax),$this.numcellsx * $this.cellcount);
}else{
$g.DrawLine($this.redpen, $this.round($this.laseroffsetx * $this.dpmm), 0, $this.round($this.laseroffsetx * $this.dpmm),$this.numcellsx * $this.cellcount);
}
$g.DrawLine($this.redpen, 0, $this.round($this.laseroffsety * $this.dpmm), $this.numcellsy * $this.cellcount, $this.round($this.laseroffsety * $this.dpmm));
}else{
$g.Clear([System.Drawing.Color]::White);
}
if ($this.rectangles.Count -gt 0){
#Write-Debug "Painting permanent rectangle";
$g.FillRectangles($this.blackbrush, $this.rectangles);
$g.DrawRectangles($this.blackpen, $this.rectangles);
$this.pictureBox1.Invalidate();
}
if(($this.isMouseDown)){
#Write-Debug "Painting temporary rectangle";
[System.Drawing.Rectangle] $rect = $this.getNewRectangle();
$g.DrawRectangle($this.redpen, $rect);
}
$this.pictureBox1.Invalidate();
}
pictureBox1_MouseMove([object] $sender, [System.Windows.Forms.MouseEventArgs] $m){
#Write-Debug "Move";
$this.lastPoint = $m.Location;
}
pictureBox1_MouseDown([object] $sender, [System.Windows.Forms.MouseEventArgs] $m){
#Write-Debug "Mouse down";
$this.startPoint = $m.Location;
$this.lastPoint = $m.Location;
$this.isMouseDown = $true;
}
pictureBox1_MouseUp([object] $sender, [System.Windows.Forms.MouseEventArgs] $e){
#Write-Debug "Mouse Up";
if($this.isMouseDown){
$this.isMouseDown = $false;
[System.Drawing.Rectangle] $rect = $this.getNewRectangle();
if (($rect.Width -gt 0) -and $rect.Height -gt 0){
#Write-Debug "Rectangle added";
$this.rectangles += $rect;
}
$this.lastPoint = [System.Drawing.Point]::Empty;
$this.startPoint = [System.Drawing.Point]::Empty;
Write-Debug "Rectangle Count: $($this.rectangles.Count)";
}
}
clearButton_Click([object] $sender, [EventArgs] $e){
Write-Debug ($sender | ConvertTo-Json);
if (($sender.Name -eq "clearButton") -and $this.rectangles.Count -gt 0){
Write-Debug "Clearing";
$this.rectangles = $null
$this.pictureBox1.Invalidate();
}
}
getScreenScaleFactor(){
$rh=[int](Get-CimInstance -ClassName Win32_VideoController)[0].CurrentVerticalResolution;
$vh=[int][System.Windows.Forms.SystemInformation]::VirtualScreen.Height;
Write-Debug "rh: $($rh)`nvh: $($vh)";
if(($rh -eq 0) -or $vh -eq 0){
$this.screenscalefactor = 1;
}else{
$this.screenscalefactor = $rh / $vh;
}
}
getDPI(){
$this.dpi = (Get-ItemProperty -Path "HKCU:\Control Panel\Desktop\").LogPixels;
if($this.dpi -eq 0){
$this.dpi = (Get-ItemProperty -path "HKCU:\Control Panel\Desktop\WindowMetrics").AppliedDPI;
}
if($this.dpi -eq 0){
$this.dpi = (Get-ItemProperty -path "HKCU:\Control Panel\Desktop\PerMonitorSettings\*").DpiValue;
}
if($this.dpi -eq 0){
$this.dpi = 96;
}
Write-Debug "getDPI: $($this.dpi)";
}
MainForm_FormClosing([object] $sender, [System.Windows.Forms.FormClosingEventArgs] $f){
#[System.Windows.Forms.MessageBox]::Show("Hello $($sender.Name).`n`n$($e)" , "Default");
$this.millerTime();
}
}
Set-Location $PSScriptRoot;
enum MCode{
M106 = 106; #Laser Power
M420 = 420; #Bed Leveling
}
enum GCode{
G0 = 0; #Non-etch move
G1 = 1; #Etch Move
G4 = 4; #Wait S seconds or P milliseconds
G21 = 21; #Set to mm
G90 = 90; #Absolute positioning
G91 = 91; #Relative positioning
}
enum ScanDirection{
X = 0;
Y = 1;
}
enum MirrorAxis{
OFF = 0;
X = 1;
Y = 2;
}
enum Origin{
TopLeft = 0;
TopRight = 1;
BottomLeft = 2;
BottomRight = 3;
Center = 4;
}
class Position{ #A position such as a pixel x and y coordinate
[double] $x = 0;
[double] $y = 0;
Position([double] $x, [double] $y){
$this.x = $x;
$this.y = $y;
}
}
class ImagePixel{
[System.Drawing.Color] $color;
[Position] $imageposition;
[double] $grey;
[byte] $negativered;
[byte] $negativegreen;
[byte] $negativeblue;
ImagePixel([int] $x, [int] $y, [System.Drawing.Color] $color){
$this.imageposition = [Position]::new($x,$y);
$this.color = $color;
}
convertToGrey(){
$this.grey = ($this.color.R * 0.2126) + ($this.color.G * 0.7152) + ($this.color.B * 0.0722) # Luminosity Method
}
convertToNegative(){
$this.negativered = 255 - $this.color.R;
$this.negativegreen = 255 - $this.color.G;
$this.negativeblue = 255 - $this.color.b;
}
}
class EtcherSettings{
[int] $spotsizex; #microns
[int] $spotsizey;
[int] $bedsizex; #mm
[int] $bedsizey;
[byte] $thresholdpower;
[double] $laseroffsetx;
[double] $laseroffsety;
[double] $defaultfocus; #mm Distance from laser aperture to focus point
[bool] $levelingenabled;
[double] $h_pixelsperdot;
[double] $v_pixelsperdot;
EtcherSettings(){
$this.spotsizex = 100;
$this.spotsizey = 100;
$this.bedsizex = 220;
$this.bedsizey = 220;
$this.thresholdpower = 5;
$this.laseroffsetx = -12;
$this.laseroffsety = 55;
$this.defaultfocus = 42.18;
$this.levelingenabled = $true;
}
}
class ProcessSettings{
[byte] $minlaserpower;
[byte] $maxlaserpower;
[byte] $previouspower;
[byte] $whitethreshold;
[int] $skipspeed; #mm/sec
[int] $etchspeed; #mm/sec
[int] $fillspacing; #How much, in percent, to retard the move to the next line (think overlap when mowing the lawn)
[bool] $invertcolor; #Make black white and white black?
[bool] $mirrorimage; #Switch the direction of the pixels
[MirrorAxis] $mirroron; #the axis on which to flip the image (requires $mirrorimage to be true)
[Origin] $imageorigin; #The start position to analyze the image
[Origin] $etcherorigin; #The start position, or 0,0 point, of the printer/etcher
[ScanDirection] $scandirection; #Which way to etch the sample?
[int] $cycles; #How many times to go over the same area
[int] $scanlines; #The number of scan lines to process the image
[int] $timeestimate; #An estimate of how long it's going to take to etch the image
[String] $gcode;
ProcessSettings(){
$this.maxlaserpower = 255;
$this.minlaserpower = 127;
$this.previouspower = 0;
$this.whitethreshold = 230;
$this.skipspeed = 10;
$this.etchspeed = 10;
$this.invertcolor = $false;
$this.mirrorimage = $false;
$this.mirroron = [MirrorAxis]::OFF;
$this.imageorigin = [Origin]::TopLeft; #I think all image handling software considers top-left to be origin
$this.etcherorigin = [Origin]::BottomLeft;
$this.scandirection = [ScanDirection]::X;
$this.cycles = 1;
$this.fillspacing = 50;
$this.scanlines = 0;
$this.timeestimate = 0;
$this.gcode = "";
}
}
class EtchImage{
[String] $path;
[String] $filename;
[String] $filenamenoext;
[String] $extension;
[System.Drawing.Bitmap] $file;
[ImagePixel] $firstpixel; #the first pixel that's not white (or black if inverted)
[ImagePixel] $lastpixel; #the last pixel that's not white (or black if inverted)
[int] $etchpixels; #the number of image pixels that are over the threshold
[double] $horizontal_dpmm;
[double] $vertical_dpmm;
[double] $totalpixels;
EtchImage(){
}
}
class ImageToGCode{
[EtcherSettings] $etchersettings;
[ProcessSettings] $processsettings;
[EtchImage] $image; #The image to be processed
[Position] $offsets;
[String] $basepath = $PSScriptRoot;
[String] $inputpath = $null;
[String] $outputpath = $null;
ImageToGCode(){
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing");
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms");
$this.etchersettings = [EtcherSettings]::new();
$this.processsettings = [ProcessSettings]::new();
$this.image = [EtchImage]::new();
$this.offsets = [Position]::new(0,0);
}
ImageToGCode([String] $path){
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing");
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms");
$this.etchersettings = [EtcherSettings]::new();
$this.processsettings = [ProcessSettings]::new();
$this.image = [EtchImage]::new();
$this.offsets = [Position]::new(0,0);
$this.inputpath = $path;
$this.getFileFromPath();
$this.start();
}
start(){
$this.processImage();
}
millerTime(){
#Thank you, Ron
#$this.image.file.Dispose();
$this.outputpath = "$($this.image.path)\$($this.image.filenamenoext).gcode";
$this.processsettings.gcode | Out-File -FilePath $this.outputpath;
[System.IO.File]::WriteAllLines($this.outputpath, $this.processsettings.gcode);
Write-Host "File saved to $($this.outputpath)";
}
processImage(){
if($this.inputpath -eq $null){
$this.selectFile()
}
if($this.image){
Write-Debug "Image is okay";
$this.getImageMetadata();
$this.initializeOffsets();
$this.initializeGCode();
$this.scanImage();
$this.finalizeGCode();
$this.millerTime();
}
}
[bool] selectFile() {
$VerbosePreference = "Continue";
$FileBrowser = New-Object System.Windows.Forms.OpenFileDialog -Property @{
InitialDirectory = [Environment]::GetFolderPath('MyComputer');
Filter = 'Image Files (*.jpg,*.jpeg,*.png,*.tif,*.tiff,*.bmp)|*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp';
Multiselect = $false;
CheckFileExists = $false;
CheckPathExists = $false;
ValidateNames = $false;
}
$null = $FileBrowser.ShowDialog();
Write-Verbose ($FileBrowser | ConvertTo-Json -Depth 100);
if($($FileBrowser.FileName)){
$this.image.path = (Split-Path -Path $($FileBrowser.FileName) -Parent);
$this.image.filename = $FileBrowser.SafeFileName;
$this.image.filenamenoext = [IO.Path]::GetFileNameWithoutExtension($this.image.filename);
$this.image.extension = ($this.image.filename -split "\.")[-1];
Write-Debug ($this.image | ConvertTo-Json -Depth 100);
$this.image.file = [System.Drawing.Bitmap]::Fromfile($FileBrowser.FileName);
return $true;
}
return $false;
}
getFileFromPath(){
try{
if((Test-Path -LiteralPath $this.inputpath)){
$this.image.path = (Split-Path -Path $($this.inputpath) -Parent);
$this.image.filename = [System.IO.Path]::GetFileName($this.inputpath);
$this.image.filenamenoext = [System.IO.Path]::GetFileNameWithoutExtension($this.inputpath);
$this.image.extension = ($this.image.filename -split "\.")[-1];
$this.image.file = [System.Drawing.Bitmap]::Fromfile($this.inputpath);
}
}catch{
Write-Warning "Cannot find file from path";
}
}
scanImage(){
if($this.processsettings.scandirection -eq [ScanDirection]::X){
Write-Verbose "Scanning direction is X";
for($y = 0; $y -lt $this.image.file.Height; $y++){
Write-Host "Row $($y) of $($this.image.file.Height)";
for($x = 0; $x -lt $this.image.file.Width; $x++){
$this.processPixel($x, $y);
}
$this.processsettings.scanlines += 1;
}
}else{
Write-Verbose "Scanning direction is Y";
for($x = 0; $x -lt $this.image.file.Width; $x++){
Write-Host "Row $($x) of $($this.image.file.Width)";
for($y = 0; $y -lt $this.image.file.Height; $y++){
$this.processPixel($x, $y);
}
$this.processsettings.scanlines += 1;
}
}
Write-Debug "Pixels to be processed: $($this.image.etchpixels)";
Write-Debug ($this.image | ConvertTo-Json);
Write-Debug "It will take approximately $($this.processsettings.timeestimate) seconds to process.";
Write-Debug "There are $($this.processsettings.scanlines ) scanlines.";
Write-Debug "Etcher Settings: $($this.etchersettings | ConvertTo-Json )";
Write-Debug "Process Settings: $($this.processsettings | ConvertTo-Json)";
}
processPixel([int] $x, [int] $y){
[ImagePixel] $pixel = [ImagePixel]::new($x,$y,($this.image.file.GetPixel($x,$y)));
if($this.checkColor($pixel)){
#the pixel is good!
$this.processLaserData($pixel);
$this.image.etchpixels += 1;
if($this.image.etchpixels -eq 1){
$this.image.firstpixel = $pixel;
}
$this.image.lastpixel = $pixel;
}
}
[bool] checkColor([ImagePixel] $pixel){
$invertedthreshold = 255 - $this.processsettings.whitethreshold;
if($this.processsettings.invertcolor){
$pixel.convertToNegative();
$pixel.convertToGrey();
if($pixel.grey -le $invertedthreshold){
return $true;
}
}else{
$pixel.convertToGrey();
if($pixel.grey -le $this.processsettings.whitethreshold){
return $true;
}
}
return $false;
}
processLaserData([ImagePixel] $pixel){
$power = ($this.convertLaserPower($pixel.grey));
if($this.processsettings.previouspower -ne $power){
$this.processsettings.gcode += "$([MCode]::M106) S$($power)`n";
$this.processsettings.gcode += "$([GCode]::G4) P100`n";
$this.processsettings.previouspower = $power;
}
[double] $fillspacingpermm = 0;
if($this.processsettings.scandirection -eq [ScanDirection]::X){
if($this.processsettings.scanlines -gt 0){
$fillspacingpermm = (($this.etchersettings.spotsizey / 1000) * ($this.processsettings.fillspacing / 100));
}
$x = $this.offsets.x + (( $pixel.imageposition.x) / ($this.image.horizontal_dpmm ) );
$y = $this.offsets.y + (($pixel.imageposition.y + $fillspacingpermm) / ($this.image.vertical_dpmm ) );
$f = $this.getFSpeed($this.processsettings.etchspeed);
$this.processsettings.gcode += "$([GCode]::G1) X$([Math]::Round($x,5)) Y$([Math]::Round($y,5)) F$($f)`n";
}else{
if($this.processsettings.scanlines -gt 0){
$fillspacingpermm = ($this.etchersettings.spotsizex / 1000) * ($this.processsettings.fillspacing / 100);
}
$x = $this.offsets.x + ($pixel.imageposition.x + $fillspacingpermm);
$y = $this.offsets.y + $pixel.imageposition.y ;
$f = $this.getFSpeed($this.processsettings.etchspeed);
$this.processsettings.gcode += "$([GCode]::G1) X$($x) Y$($y) F$($f)`n";
}
}
initializeOffsets(){
#$this.addOffset(([Position]::new(($this.etchersettings.bedsizex / 2), ($this.etchersettings.bedsizey / 2))));
#$this.addOffset(([Position]::new($this.etchersettings.laseroffsetx, -$this.etchersettings.laseroffsety)));
$this.addOffset(([Position]::new((($this.etchersettings.spotsizex/1000) / 2), (($this.etchersettings.spotsizey / 1000) / 2))));
Write-Debug "Current Offsets: $($this.offsets | ConvertTo-Json)";
}
initializeGCode(){
$this.processsettings.gcode = ";Created using the ImageToGcode Powershell script from Laser-Lance (https://www.laserlance.com)`n";
$this.processsettings.gcode += ";LAYER_COUNT:0`n;LAYER:0`n"; #OctoPrint complains if it doesn't see a layer marker
$this.processsettings.gcode += "$([GCode]::G90); From this moment, all movements are absolute`n";
$this.processsettings.gcode += "$([GCode]::G21); Set units to mm`n";
$this.processsettings.gcode += "$([MCode]::M106) S0; Turn the laser off`n";
$this.processsettings.gcode += "$([GCode]::G1) F$($this.getFSpeed($this.processsettings.etchspeed)); Set default feed speed to the etch speed`n";
$this.processsettings.gcode += "$([GCode]::G0) X$($this.offsets.x) Y$($this.offsets.y) Z$($this.etchersettings.defaultfocus) F$($this.getFSpeed($this.processsettings.skipspeed)); Offset from the nozzle to center lase`n";
#$this.processsettings.gcode += "$([GCode]::G91); From this moment, all movements are relative`n";
}
finalizeGCode(){
$this.processsettings.gcode += "`n$([MCode]::M106) S0; Turn off the laser`n";
#$this.processsettings.gcode += "$([GCode]::G90); From this moment, all movements are absolute`n";
$this.processsettings.gcode += "`n$([GCode]::G0) X$($this.offsets.x) Y$($this.offsets.y)";
}
[byte] convertLaserPower([double] $grey){
$power = ($this.mapValue($grey,255,0,$this.processsettings.minlaserpower,$this.processsettings.maxlaserpower));
#Write-Debug "Power $([Math]::Round($power))";
return ([Math]::Round($power));
}
[double] mapValue([double] $value, [double] $fromLow, [double] $fromHigh, [double] $toLow, [double] $toHigh) {
$fromRange = $fromHigh - $fromLow;
$toRange = $toHigh - $toLow;
$scaleFactor = $toRange / $fromRange;
# Re-zero the value within the from range
$tmpValue = $value - $fromLow;
# Rescale the value to the to range
$tmpValue *= $scaleFactor;
# Re-zero back to the to range
return ($tmpValue + $toLow);
}
addOffset([Position] $offsets){
$this.offsets.x += $offsets.x;
$this.offsets.y += $offsets.y;
}
[int] getFSpeed([int] $speed){
return ($speed * 60); #mm/sec to mm/min
}
getImageMetadata(){
$this.getTotalPixels();
$this.getPixelsPerMM();
$this.getPixelsPerMM();
$this.getPixelsPerDot();
}
getTotalPixels(){
$this.image.totalpixels = $this.image.file.Width * $this.image.file.Height;
}
getPixelsPerMM(){
$this.image.horizontal_dpmm = $this.image.file.HorizontalResolution / 25.4;
$this.image.vertical_dpmm = $this.image.file.VerticalResolution / 25.4;
}
getPixelsPerDot(){
$this.etchersettings.h_pixelsperdot = ($this.etchersettings.spotsizex / 1000) * $this.image.horizontal_dpmm;
$this.etchersettings.v_pixelsperdot = ($this.etchersettings.spotsizey / 1000) * $this.image.vertical_dpmm;
}
}
Set-Location $PSScriptRoot;
class GCodeToImage{
[String] $basepath = $PSScriptRoot;
[String] $inputpath = $null;
[String] $outputpath = $null;
[int] $bedx = 830; #220mm 96dpi = 8.66 inches or 830 pixels
[int] $bedy = 830;
[int] $dpi = 0;
[double] $dpmm;
[System.Drawing.Bitmap] $bitmap;
[System.Drawing.Graphics] $graphics;
[byte] $red = 0;
[byte] $green = 0;
[byte] $blue = 0;
[int] $xpixel = 0;
[int] $ypixel = 0;
[String] $gcode = "";
[String] $filter = "([^G0|^G1][X|Y]\d+.\d+)";
[Regex] $regex;
[Object] $matches = @{};
[double] $screenscalefactor;
GCodeToImage(){
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing");
}
GCodeToImage([int] $bedx, [int] $bedy){
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing");
$this.bedx = $bedx;
$this.bedy = $bedy;
$this.start();
}
GCodeToImage([String] $inputpath, [String] $outputpath){
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing");
$this.inputpath = $inputpath;
$this.outputpath = $outputpath;
$this.start();
}
[bool] start(){
if(!$this.inputpath){
$this.getInputFile();
}
$this.getDPI();
$this.getScreenScaleFactor();
$this.dpmm = ($this.dpi * $this.screenscalefactor) / 25.4;
Write-Verbose "dpi: $($this.dpi)";
Write-Verbose "dpmm: $($this.dpmm)";
$this.bedx = [Math]::Round((220 * $this.dpmm)) + 1;
$this.bedy = [Math]::Round((220 * $this.dpmm)) + 1;
Write-Verbose "bedx: $($this.bedx)";
Write-Verbose "bedy: $($this.bedy)";
$this.bitmap = [System.Drawing.Bitmap]::new($this.bedx, $this.bedy);
$this.graphics = [System.Drawing.Graphics]::FromImage($this.bitmap);
$this.graphics.Clear([System.Drawing.Color]::White);
$this.regex = [Regex]::new($this.filter);
$this.getGCodeFromFile();
$this.parseGCode();
$this.saveFile();
$this.millerTime();
return $true;
}
[bool] getInputFile(){
$FileBrowser = New-Object System.Windows.Forms.OpenFileDialog -Property @{
InitialDirectory = [Environment]::GetFolderPath('MyComputer');
Filter = 'GCode (*.gcode,*.GCODE)|*.gcode;*.GCODE;';
Multiselect = $false;
CheckFileExists = $true;
CheckPathExists = $true;
ValidateNames = $false;
}
$null = $FileBrowser.ShowDialog();
Write-Verbose ($FileBrowser | ConvertTo-Json -Depth 100);
if($($FileBrowser.FileName)){
$this.inputpath = $FileBrowser.FileName;
return $true;
}
return $false;
}
[bool] getOutputFile(){
if($this.inputpath){
$this.outputpath = "$($(Split-Path $this.inputpath))\$($([System.IO.Path]::GetFileNameWithoutExtension($this.inputpath)))g.bmp";
return $true;
}else{
Write-Host "No file selected. Cancelling.";
break;
return $false;
}
<#
$FileBrowser = New-Object System.Windows.Forms.SaveFileDialog -Property @{
InitialDirectory = [Environment]::GetFolderPath('MyComputer');
Filter = 'Image Files (*.jpg,*.jpeg,*.png,*.tif,*.tiff,*.bmp)|*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp';
CheckFileExists = $false;
CheckPathExists = $false;
ValidateNames = $false;
}
$null = $FileBrowser.ShowDialog();
if($($FileBrowser.FileName)){
$this.outputpath = $FileBrowser.FileName;
return $true;
}else{
Write-Host "No file selected. Cancelling.";
break;
return $false;
}
#>
}
getGCodeFromFile(){
$this.gcode = Get-Content -LiteralPath $this.inputpath;
}
parseGCode(){
$this.matches = $this.regex.Matches($this.gcode);
for($i = 0; $i -lt $this.matches.Count; $i+=2){
$this.xpixel = (([int]([String] $this.matches[$i]).Replace("X",""))) * $this.dpmm;
$this.ypixel = (([int]([String] $this.matches[$i + 1]).Replace("Y",""))) * $this.dpmm;
if(($this.xpixel -gt $this.bedx) -or ($this.xpixel -lt 0)){
$this.xpixel = $this.bedx;
}
if(($this.ypixel -gt $this.bedy) -or ($this.ypixel -lt 0)){
$this.ypixel = $this.bedy;
}
Write-Verbose "x: $($this.xpixel)";
Write-Verbose "y: $($this.ypixel)";
$this.bitmap.SetPixel($this.xPixel, $this.yPixel, [System.Drawing.Color]::FromArgb($this.red, $this.green, $this.blue));
}
}
saveFile(){
if(!$this.outputpath){
$this.getOutputFile();
}
$this.bitmap.Save($this.outputpath);
if(!(Test-Path -LiteralPath $this.outputpath)){
Write-Host "Error saving file";
}else{
#Invoke-Item $path;
}
}
getScreenScaleFactor(){
$rh=[int](Get-CimInstance -ClassName Win32_VideoController)[0].CurrentVerticalResolution;
$vh=[int][System.Windows.Forms.SystemInformation]::VirtualScreen.Height;
Write-Debug "rh: $($rh)`nvh: $($vh)";
if(($rh -eq 0) -or $vh -eq 0){
$this.screenscalefactor = 1;
}else{
$this.screenscalefactor = $rh / $vh;
}
}
getDPI(){
$this.dpi = (Get-ItemProperty -Path "HKCU:\Control Panel\Desktop\").LogPixels;
if($this.dpi -eq 0){
$this.dpi = (Get-ItemProperty -path "HKCU:\Control Panel\Desktop\WindowMetrics").AppliedDPI;
}
if($this.dpi -eq 0){
$this.dpi = (Get-ItemProperty -path "HKCU:\Control Panel\Desktop\PerMonitorSettings\*").DpiValue;
}
if($this.dpi -eq 0){
$this.dpi = 96;
}
Write-Debug "getDPI: $($this.dpi)";
}
millerTime(){
$this.bitmap.Dispose();
$this.graphics.Dispose();
}
}
One Response
You spew code as elegant as a chicken laying a golden egg.