Search This Blog

Saturday, July 27, 2013

Arduino XYZ CNC / 3D Printer Do-over


Now that I've polished my Java skills over the past couple years I wanted to come back and improve a previous project of an XYZ CNC machine that was controlled by Arduinos and a crude PC Java program.

This is the hardware I'm using.  I've covered how to build all these pieces in earlier blog posts.  As I work through the pieces I'll refresh the info on the hardware and sketches.


I'll need to make some software updates to the Arduinos, but the original Arduino sketches are posted on my download pages.    I want to improve the handshaking, auto detect the arduinos, and process HPGL files better, plus make a better GUI.

There are three Arduinos.  The first is the XYZ motor controller that uses two motor shields from ADAfruit.  I posted a hack on how to attach two to the same Arduino.  I also replaced the motor driver chips with some higher capacity chips and put on heat sinks.  The reason one arduino does all this work is I absolutely want to synchronize the x, y and z moves.  I don't want to risk dropping a move or mistiming a move due to USB communication glitches.  This arduino gets HPGL format commands that give position from and to commands and it calculates all the sub stepping to get there.

The second arduino only controls the speed and direction of the cutter.  The cutter is a cordless drill motor and chuck controlled by some heavy duty MOSFET switches.

The third arduino is connected to position encoders and an LCD.  It reads back the position and also displays it and any other messages the machine has to say.   This is mostly used as a watchdog, since the steppers count position.  If the position gets off from what the machine thinks it should be I should stop and let the user figure things out.

Power is supplied by a old PC desktop power supply.

My PC original control program was very crude and didn't work if the Arduinos were in different COM ports and didn't have file spooling capability.  I'm way better at coding now, that was my very first program after "Hello World".

So my project this time is to write a respectable java program that will work for everyone, It will read/spool in a standard HPGL command file and control the CNC.  It will also have buttons for manual controlling the CNC.

I'm using Java and the netbeans development environment, which is free from oracle.
I'm starting with the basic Java serial tool I just wrote for the PC that will communicate with the Arduinos.

http://blog.workingsi.com/2013/01/revisiting-arduino-control-from-pc.html

The first piece will be a startup routine that finds all three Arduinos and establishes a link.  Each arduino sketch will be updated to send an initialization sequence I can look for.

After that there will be a choice of entering manual mode to push buttons on the GUI to move, or load an HPGL drawing file.  There should be a routine for setting the home position.

First I'm going to work on communicating with the position sensor I described in this post:
http://blog.workingsi.com/2011/02/position-sensor-for-arduino-xyz-cnc.html
I fired it up with the serial tool and the original code in the post is working fine, the encoder is constantly spitting out the position on the COM port as fast as it can.  For now I shouldn't need to alter the Arduino sketch.  My PC program will have to seek and identify this data stream and map it properly.

Next I fired up the XYZ stepper Arduino and that seems to be in good order as well.  The original sketch was built from this post.  You need to use the custom library to use all four steppers
http://blog.workingsi.com/2011/03/method-for-controlling-4-steppers-from.html
and the sketch for the XYZ control is posted on my download site:
http://code.google.com/p/arduino-java-xyzcnc/downloads/detail?name=XYZ_CNC_HPGL_r13.pde&can=2&q=#makechanges

Writing the Java to connect to all the Arduinos

First step is to perfect the serial port autodetection.  I'll use the touchport command and then look for a unique broadcast from each Arduino in order to assign them to ports.

After a long stall, I finally got this working in a recent project.
http://blog.workingsi.com/2013/07/lcd-system-monitor-for-pc-case-bling.html

I had been hung up from a mistake.  I used touchPort, to get the port names, and then instantiated a serial class to talk to each one.  However that process wiped out my list of ports so the loop failed.   Also I learned I have to wait and let the listener have time to hear each Arduino before moving on.  Here are the basic pieces.

This Java loop goes through all the ports:
     int iii=0;
        while (clientPortName.equals("")) {
                                                        
            //scan for devices and list
            serial.touchPort("", Integer.valueOf(serialRates.getSelectedItem().toString()));
                    jListPorts.setModel(new javax.swing.AbstractListModel() {
                    String[] strings = serial.portNames;
                    public int getSize() { return strings.length; }
                    public Object getElementAt(int i) { return strings[i]; }
            });
                            
            if (!serial.portNames[iii].equals("")) {        

                System.out.println("PORT:" + serial.portNames[iii]);
                setupCom(serial.portNames[iii]);
                // give it some time to hear the Arduino calling
                long currentTimeMillis = System.currentTimeMillis();
                while (System.currentTimeMillis()<currentTimeMillis+3000 ) {
                    //just hang out hoping for a letter
                }        
            }
          iii++;
          if (iii>19) {
              System.out.println("Exiting, Arduino not found");
              System.exit(1);
          }
        }



This routine below grabs each port and sets up a listener and sets a flag that breaks the loop above if the desired character is found.

       public void setupCom(final String portName) throws SerialException{
                //System.out.println(">>"+portName);
                serial.dispose();
                serial = new Serial(portName, Integer.valueOf(serialRates.getSelectedItem().toString()), 'E', 8, 2 );
                
        //Add the listener to pipe remote responses 
        serial.addSerialEventListener(new Listener_SerialEvent() {
            @Override
            public void SerialEventOccurred(int message) {
                String message_out = formatRead(message);           
                
                if (message_out.equalsIgnoreCase("Q")) {
                    System.out.println(portName + " detected Q - gotcha!!!");
                    clientPortName = portName;
                    send("Hello World!");  //respond so Arduino stops sending Q
                }
            }
        });
    }

I don't have to break the upper loop, I could go back and set up the port at the end of looping, but this is faster if I stop when the right port is found.

I've updated the serial Arduino tool with a field for the character is it going to look for to connect. In this picture the GUI has three Arduinos connected.  One is transmitting Z, one is busy, and one is transmitting A.  The one transmitting Z , COM4, is grabbed and now send and receive can begin.

Download the executable here:
https://code.google.com/p/arduino-java-xyzcnc/downloads/detail?name=SifishSerialArdunioTool.jar&can=2&q=

Now I will use multiple of these tools to search and connect to the three Arduinos.
I've got that code working, each Arduino is set to broadcast a different character to be found by the serial comm port scanner.  However that means I had to resurrect the old arduino code and modify it.

I had no trouble with the encoder program https://code.google.com/p/arduino-java-xyzcnc/downloads/list, the rotary encoder sketch.  I was able to add the establishContact() function from the Arduino serial response example.

The XYZ motor controller program is broken.  The old AFMotor.h libraries I had been using from Adafruit don't seem to be working, they don't compile in the new release.   I updated new library from here:
http://learn.adafruit.com/adafruit-motor-shield/downloads
that library seems OK, but my modified AFMotorA.h library for use in stacking shields is broken.  maybe I need to remake it.

The big problem turned out to be that now Arduino installs to the Program Files (x86) directory, which is read only and blocked by default.   I had to make all the library files unblocked by right clicking and selecting properties.  In the process I did remake the AFMotorA library.  All I did was search and replace AFMotor for AFMotorA and AF_Stepper with AF_StepperA, and microsteppercurve to microsteppercurveA in the .cpp, .h and keywords files.  Also rename the files AFMotorA.cpp and AFMotorA.h.   Now the old sketch compiles pointing to the AFMotor libraries.

I'm using three instances of my serial tool with only slight modification.  One for each arduino.
Had to do some mundane coding to pass the serial responses up to the main Arduino GUI by adding listeners to each of the serial port GUIs and passing them up to the main GUI.

The connection process takes so long that I want to move it into a thread by itself rather than locking up the GUI for 15 seconds.  that fixed a lot of weird problems I was having with the GUI not drawing, messages not scrolling etc.

Cool, now I have a main GUI that when you press connect, runs out and finds 3 different Arduinos and scrolls a log of what it is doing.   This simple task had eluded me for a while due to stupid mistakes.


Now the fun part, re-writing the code to actually move the XYZ machine.

Cleaning up the Arduino Encoder Program & Sketch

Updated the code to pull the position from the Arduino, parse the string and display it.  I'm finding the response time from the Arduino is really slow between when the encoders and LCD report the change, and when the string shows up on the serial bus.  I'm going to change the encoder code to only print to serial when there is a change to reduce the traffic and hopefully prevent the backlog.  It worked!  Now I only send serial traffic when there is a change, and response is much faster.

Realized I need a function for the Encoder program to tell it to reset it's counters.  That is for a Home position command.  Added that and updated the position encoder program.  Also found a bug that the position counters were unsigned integers!!  The LCD calculation seemed right but the serial information signs were mixed up.  Fixed it.  It is working much better now!

 /* Rotary encoder readout for XYZ CNC machine */

// Encoders connected as follows
// X encoder PIN A - Analog 0
// X encoder PIN B - Analog 1
// Y encoder PIN A - Analog 2
// Y encoder PIN B - Analog 3
// Z encoder PIN A - Analog 4
// Z encoder PIN B - Analog 5
// Pin C of all encoders is grounded, center pin of http://www.sparkfun.com/products/9117
// thus variable PINC will read this entire register simultaneously
// this avoids missing states of the encoder
//Based on 
//http://www.circuitsathome.com/mcu/reading-rotary-encoder-on-arduino
//Expanded to 3 encoders and written to be more clear and less over elegant

//Variables to set the counts/inch
#define TH_PER_IN  20 //lead screw threads per inch
#define CT_PER_REV  48 //encoder counts per revolution
// Thus the accuracy of the measurement 1 inch /(TH_PER_IN*CT_PER_REV)

//Variable to reverse the sign if the machine and encoder don't agree with your sense of things
// set to 1 or -1
#define REFLECTX -1
#define REFLECTY -1
#define REFLECTZ 1

#include <LiquidCrystal.h>
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);

unsigned int olddata;
unsigned int newdata;
char newX;
char newY;
char newZ;
char oldX;
char oldY;
char oldZ;
long signed int counterX = 0;
long signed int counterY = 0;
long signed int counterZ = 0;
char inByte = 0;         // incoming serial byte
// table of meaning of states, input is two bits old value, two bits new value
// the encoder has either moved +1, -1 or stayed in the same place
int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0}; 

void setup()
{
  /* Setup encoder pins as inputs */
  // http://www.arduino.cc/en/Reference/PortManipulation
  DDRC = B00000000;   //Sets input direction all to analog inputs
  PORTC = B11111111;   //Sets pull ups on the analog pins to 1
  Serial.begin (9600);
  establishContact();
  Serial.println("Encoders Found");
  counterX = 0;
  counterY = 0;
  counterZ = 0;
  
  pinMode(13 ,OUTPUT);  //turn on backlight
  digitalWrite(13, HIGH); //turn on backlight
  lcd.begin(20,4);
  delay(500); //Optrex display needs to rest after a begin
  lcd.clear();
  lcd.setCursor(0,0);
  delay(100); //Optrex display needs to rest after setting cursor
  lcd.print("XYZ CNC Position");
  lcd.setCursor(0,1);
  lcd.print("X: ");
  lcd.setCursor(0,2);
  lcd.print("Y: ");
  lcd.setCursor(0,3);
  lcd.print("Z: ");
  
  //send initial position
   Serial.print(1000*counterX/(TH_PER_IN*CT_PER_REV*REFLECTX), DEC); Serial.print(":");
   Serial.print(1000*counterY/(TH_PER_IN*CT_PER_REV*REFLECTY), DEC); Serial.print(":");
   Serial.print(1000*counterZ/(TH_PER_IN*CT_PER_REV*REFLECTZ), DEC); 
   Serial.println(";");
      
}

void loop()
{
  //This loop needs to run fast to avoid missing encoder states
  newdata = PINC;
  
  if( newdata != olddata ) {
    
    newX = newdata & 0x03;  //mask out just the X bits
    oldX = olddata & 0x03;  //mask out just the X bits
    oldX = oldX << 2;  //move over to form address to table
    counterX += enc_states[oldX + newX] ;  //increment or decrement counter from table

    newY = newdata & 0x0C;   //mask out just the Y bits
    newY = newY >> 2;  //shift to LSBs
    oldY = olddata & 0x0C;   //mask out just the Y bits
    counterY += enc_states[oldY + newY] ; 
    
    newZ = newdata & 0x30;   //mask out just the Z bits
    newZ = newZ >> 4;  //shift to LSBs
    oldZ = olddata & 0x30;   //mask out just the Z bits
    oldZ = oldZ >> 2;  //shift to form address to table
    counterZ += enc_states[oldZ + newZ] ;
    
    // only send updates over serial bus when data has changed to reduce traffic
     Serial.print(1000*counterX/(TH_PER_IN*CT_PER_REV*REFLECTX), DEC); Serial.print(":");
     Serial.print(1000*counterY/(TH_PER_IN*CT_PER_REV*REFLECTY), DEC); Serial.print(":");
     Serial.print(1000*counterZ/(TH_PER_IN*CT_PER_REV*REFLECTZ), DEC); 
     Serial.println(";");
      
  }  else {
    // only update the display when the data is NOT changing, this speeds up polling
    lcd.setCursor(3,1);
    lcd.print(1000*counterX/(TH_PER_IN*CT_PER_REV*REFLECTX), DEC);
    lcd.print(" mils    ");
    lcd.setCursor(3,2);
    lcd.print(1000*counterY/(TH_PER_IN*CT_PER_REV*REFLECTY), DEC);
    lcd.print(" mils    ");
    lcd.setCursor(3,3);
    lcd.print(1000*counterZ/(TH_PER_IN*CT_PER_REV*REFLECTZ), DEC);
    lcd.print(" mils    ");
    
    // check if any serial messages have come in, since not busy
    if (Serial.available() > 0) {
      // get incoming byte:
      inByte = Serial.read();
      // reset position on indicated counter
      if (inByte == 'X')  counterX = 0;
      if (inByte == 'Y')  counterY = 0;
      if (inByte == 'Z')  counterZ = 0;
      if (inByte == '@' )  {
        counterX = 0;  counterY = 0;  counterZ = 0;
      }
      // any other serial traffic respond with current position
      Serial.print(1000*counterX/(TH_PER_IN*CT_PER_REV*REFLECTX), DEC); Serial.print(":");
      Serial.print(1000*counterY/(TH_PER_IN*CT_PER_REV*REFLECTY), DEC); Serial.print(":");
      Serial.print(1000*counterZ/(TH_PER_IN*CT_PER_REV*REFLECTZ), DEC); 
      Serial.println(";");
    }
    
  }
  olddata = newdata;
}

 void establishContact() {
  while (Serial.available() <= 0) {
    Serial.print('Q');   // send a capital Q to identify myself
    delay(300);
  }
}

I realized I need to be able to calibrate and send a coordinate to the encoders and have them update their math to scale that to the known zero position.  I'll do that once I make some progress on other fronts.

Writing the GUI to Manually Control The Motors

Set up a GUI to send manual commands to the CNC machine.  Functions for Jogging X,Y, and Z by a select-able step size.  Turn on and off the tool and change it's speed.  Display the encoder's value and the calculated location both.   Finally an interpolated move command to calculate the vector to an inputted coordinate.



Next I need to go back and clean up the programming for the heavy duty motor driver.
http://blog.workingsi.com/2011/05/notes-on-heavy-duty-motor-sheild-for.html
I used a bunch of mosfets and random bipolars to convert an Adafruit motor sheild to drive a drill motor.   I left this pretty rough.  I need to clean up the control sketch for the Arduino and test it out.   I set up a slider to control the speed.  I'm not sure if I'll do active motor braking or not, depends on how things work.

Decided to update the motor controllers and eliminate one of the Arduinos!

In the process of resurrecting the heavy duty motor shield circuit, and noticing that my blog post was kinda missing a lot of information, I discovered that Adafruit had updated their motor shield and no longer sold the old one I used.   They now support stacking, which I had hacked up before.
http://www.adafruit.com/products/1438
It's totally cheating to use their shield, and maybe pricey as well, but it will save me a lot of time and what I build will be repeatable by others.  Additionally it looks like I could stack and use a single Arduino to run all four motors X, Y, Z, and Tool.  They now have a dedicated PWM chip on board. Before as well the load on the Arduino PWM was too much for four motors and I backed off to two Arduinos.  I will still need to use the heavy duty Hbridge MOSFETs for the tool motor, but that is a minor mod.   I pulled the trigger to upgrade the motor shields to the new version.  It will save me an entire Arduino.  Too bad I already wrote code, but it will be worth it.

I'm rewriting the XYZ Arduino code now to support both the XYZ and the Tool speed and use the new Adafruit v2 motor shields stacked up.  Additionally I'm going to change the architecture of the system.  Previously I had the Arduino interpreting the HPGL code directly.  That was very limiting, but I did it because I didn't know Java at the time.   A better approach is to have the PC software do the hard work, and only send specific movement commands to the Arduino.  The Arduino will still linear interpolation to move the X and Y motors together to make smooth diagonals.  I don't think it's necessary to do linear interpolation with the Z axis.

New Arduino XYZTool code

Java program will need to send the following commands to the Arduino.
Semicolon indicates command termination and begin executing.
Command set proposed:

  • R; = Reset position counters to 0:0:0 (Set Home)
  • C; = Coordinates:  Report back the current stepper counter position over the serial link for use in calibrating and catching errors
  • Pn; = Z axis relative move n steps
  • Zn; = Move to absolute Z coord
  • Mx:y; = Move relative by x:y steps
  • Ax:y; = Move to absolute coordinate x:y
  • Tn; = Set tool speed to n.  May be positive or minus.  0 is off.
  • S; = Stop all motors
  • I; = power on/initialize motors

After each command the Arduino will send back characters:

  • B; - Arduino received the command and is busy executing it
  • R; - Ready for a new command
  • Cx:y:z - Arduino counters say this is the current coordinates

I added each of these commands to the buttons in the Java program.  Now I need to jump back to the Arduino to interpret them.

The Adafruit v2 motor shields came in the mail.   I assembled them exactly like the tutorial.  I stacked two shields, and shorted the address bit on the bottom one.  One for XY and one for Z and tool.   http://learn.adafruit.com/adafruit-motor-shield-v2-for-arduino/stacking-shields
I hooked up a tiny DC motor and three hobby steppers to the shields for a mock up to use while I write the code, without worrying about running the motors into a wall, etc.
A basic motor party script with both shields worked right off the bat.


Now I'm rewriting the motor control program for the Arduino to interpret the simple commands from the Java GUI, rather than try to decode HPGL all by itself.  That was limiting the functions I could perform.   It didn't take as long as I thought it would, because I was able to reuse the heavy lifting from the old program and just change the command interpretation loops.  I still need to add the timeout and error catching stuff but it's working pretty well already!

Arduino sketch for XYZ CNC control:
/*

Program to intepret CNC commands and activate motors
Uses Adafruit v2 motor shields http://learn.adafruit.com/adafruit-motor-shield-v2-for-arduino/overview

Commands that will be received:
R; = Reset position counters to 0:0:0 (Set Home)
C; = Coordinates:  Report back the current stepper counter position over the serial link for use in calibrating and catching errors
Pn; = Z axis relative move n steps
Zn; = Move to absolute Z coord
Mx:y; = Move relative by x:y steps
Ax:y; = Move to absolute coordinate x:y
Tn; = Set tool speed to n.  May be positive or minus.  0 is off.
S; = Stop all motors
I; = Power all motors/ initialize

After each command the Arduino will send back characters:
B; - Arduino received the command and is busy executing it
R; - Ready for a new command
Cx:y:z - Arduino counters say this is the current coordinates

 */

#define INCH 4000 //steps per inch of the machine = threads per inch * steps per rotation 
#define RPM 30 //speed of the stepper motors

#include <Wire.h>
#include <Adafruit_MotorShield.h> 
//#include "utility/Adafruit_PWMServoDriver.h"

// Create the motor shield object with the default I2C address for Z axis stepper and Tool
Adafruit_MotorShield AFMS_ZT = Adafruit_MotorShield(); 
// Or, create it with a different I2C address for stacking for X and Y axis steppers
Adafruit_MotorShield AFMS_XY = Adafruit_MotorShield(0x61); 

// Connect a stepper motor with 200 steps per revolution (1.8 degree)
// to motor port #2 (M3 and M4)
Adafruit_StepperMotor *stepperZ = AFMS_ZT.getStepper(200, 2);
// And connect a DC motor to port M1
Adafruit_DCMotor *myTool = AFMS_ZT.getMotor(1);
// Connect two stepper motor with 200 steps per revolution (1.8 degree)
// to motor ports on bottom shield
Adafruit_StepperMotor *stepperX = AFMS_XY.getStepper(200, 1);
Adafruit_StepperMotor *stepperY = AFMS_XY.getStepper(200, 2);

String inputString = "";         // a string to hold incoming data
boolean stringComplete = false;  // whether the string is complete
long previousMillis = 0;        //used for timeout counter
long timeout = 10000;           //timeout length

// Set up the basic machine position variables
signed long int Xpos = 0;
signed long int Ypos = 0;
signed long int Zpos = 0;

// Set up the destination machine position variables
signed long int newXpos = 0;
signed long int newYpos = 0;
signed long int newZpos = 0;

void setup()
{
  // start serial port at 9600 bps:
  Serial.begin(9600);
  inputString.reserve(200);
  establishContact();  // send a serial byte to establish contact until receiver responds 
  
  //Start the PWM
  AFMS_XY.begin();  // create with the default frequency 1.6KHz
  AFMS_ZT.begin();  // create with the default frequency 1.6KHz
  
  //initialize motors
  initMotors();
  
  // Home position
  resetPositionCounter();
  
  Serial.println("Stepper Controller Online;");
  reportCoords();
  
}

void loop()
{
  // if we get a valid byte, read analog ins:
  if (Serial.available() > 0) {
  
    // get the new byte:
    char inChar = (char)Serial.read(); 

    // if the incoming character is a newline, set a flag
    // so the main loop can do something about it:
    if (inChar == ';') {
      stringComplete = true;
    } else {
      // add it to the inputString:
      inputString += inChar;
    }
    
    // emergency stop command, process immediatly no matter what I'm doing
    if (inChar == '*') emergencyStop();
  
    if (stringComplete) {
      //Indicate busy 
      Serial.print("B;"); 
      previousMillis = millis();         //set the timeout counter to start

      //Echo the command
      //Serial.print(inputString);
  
      //decode and do what the command said
      switch( inputString[0] ) {
        case '*' : emergencyStop(); break;
        case 'I' : initMotors();  break;
        case 'T' : setToolSpeed();  break;
        case 'C' : reportCoords();  break;
        case 'P' : relativeZMove(); break;
        case 'Z' : absoluteZMove(); break;
        case 'M' : relativeXYMove(); break;
        case 'A' : absoluteXYMove(); break;
        case 'R' : resetPositionCounter(); break;
        default  : break;
      }
       
      //clear the command
      inputString = "";
      stringComplete = false;
  
      // Indicate ready
      Serial.print("R;"); 
      
    }  

  } 
}

//    else if ((previousMillis+timeout) > millis()) {
//       //Waited too long for the end of the command
//        intputString = "";
//        stringComplete = false;
//       Serial.println("E;");
//    } 

void establishContact() {   //for initial identification
  while (Serial.available() <= 0) {
    Serial.print("~");   // send a charachter to identify myself
    delay(300);
  }
}

void emergencyStop() {   //release all motors, limit hit or user intervention
      myTool->run(RELEASE);
      stepperX->release();
      stepperY->release();
      stepperZ->release();
      //Serial.println("All motors released;");
}

void setToolSpeed() {  
    signed long int newSpeed = extractCoord(inputString, 0);
    
    if (newSpeed>0) myTool->run(FORWARD);
    else myTool->run(BACKWARD);
    
    if (newSpeed == 0) myTool->run(RELEASE);
    else myTool->setSpeed(abs(newSpeed));
}


signed long int extractCoord(String numberString, int pos) {  //pulls the signed coord from the serial input string
  // pos indicates first or second coord desired
  
  int splitPos = 0;   //location of the : character
  signed long int coord = 0;   //temp storage for coords 
  signed int sign = 1;         //temp storage for coord sign 
  char c;  
  
  //First find the position of the :
    for (int i=1; i<numberString.length(); i++) {
      if( numberString[i] == ':' ) splitPos = i ; 
  }
    
  if (splitPos ==0)  {  // there is only one coord  
    for (int i=1; i<numberString.length(); i++) {
      c = numberString[i];
      if( c == '-' ) sign = -1; // capture the sign 
      if( c >= '0' && c <= '9') coord = (10 * coord) + (c - '0') ; // convert digits to a number     
    }
  } else {
  
    switch (pos) {
      case 0 :
      for (int i=1; i<splitPos; i++) {
          c = numberString[i];
          if( c == '-' ) sign = -1; // capture the sign 
          if( c >= '0' && c <= '9') coord = (10 * coord) + (c - '0') ; // convert digits to a number     
      }
      break;
      case 1: 
        for (int i=splitPos; i<numberString.length(); i++) {
          c = numberString[i];
          if( c == '-' ) sign = -1; // capture the sign 
          if( c >= '0' && c <= '9') coord = (10 * coord) + (c - '0') ; // convert digits to a number     
        }  
      break;
      default: //something is wrong, return 0;
      break;
    }
      
  }  
  return(coord * sign);
}
  
void resetPositionCounter() {
  Xpos = 0;
  Ypos = 0;
  Zpos = 0; 
  newXpos = 0;
  newYpos = 0;
  newZpos = 0;
}  

void reportCoords() {
    Serial.print(Xpos);Serial.print(":");Serial.print(Ypos); Serial.print(":");Serial.print(Zpos);Serial.println(";");  
}

void initMotors() {
  //initialize motors
  myTool->setSpeed(RPM);   
  myTool->run(RELEASE);
  stepperX->setSpeed(RPM);  
  stepperY->setSpeed(RPM);    
  stepperZ->setSpeed(RPM);    
  
  //step motors one step and back to power the coils and hold
  stepperX->step(1, FORWARD, SINGLE); 
  stepperY->step(1, FORWARD, SINGLE);
  stepperZ->step(1, FORWARD, SINGLE); 
  stepperX->step(1, BACKWARD, SINGLE); 
  stepperY->step(1, BACKWARD, SINGLE);
  stepperZ->step(1, BACKWARD, SINGLE); 
}

void relativeZMove(){
    zMove(Zpos + extractCoord(inputString, 0));
}

void absoluteZMove(){
    zMove(extractCoord(inputString, 0));
}

void relativeXYMove(){
    linearInterpolationMove(Xpos+extractCoord(inputString, 0), Ypos+extractCoord(inputString, 1));
}

void absoluteXYMove(){
    linearInterpolationMove(extractCoord(inputString, 0), extractCoord(inputString, 1));
}

void linearInterpolationMove ( signed long int newX, signed long int newY) {
  float distance = 0;
  int stepnum = 0;
  signed long int nextX;
  signed long int nextY ;
  signed long int oldX = Xpos;
  signed long int oldY = Ypos;

  //find the hypotenuse, the total distance to be traveled
  distance = sqrt((newX - oldX)*(newX - oldX) + (newY - oldY)*(newY - oldY) ) + 0.5;

  //round to integer number of steps that distance. Step by two to minimize 0 size steps.
  for (stepnum=0; stepnum <= distance; stepnum++) {
  
    //calculate the nearest integer points along the way 
    nextX = oldX + stepnum/distance*(newX-oldX);
    nextY = oldY + stepnum/distance*(newY-oldY);

    //move machine to that new coordinate, if 0 delta, don't move
    if ((distance < 4*INCH) && (distance > -4*INCH)) { //trap crazy values
      if ((nextX-Xpos) >=  1)  stepperX->step((nextX - Xpos), FORWARD, SINGLE); 
      if ((nextX-Xpos) <= -1)  stepperX->step((Xpos - nextX), BACKWARD, SINGLE);
      if ((nextY-Ypos) >=  1)  stepperY->step((nextY - Ypos), FORWARD, SINGLE); 
      if ((nextY-Ypos) <= -1)  stepperY->step((Ypos - nextY), BACKWARD, SINGLE);
    
    //update the machine current position
    Xpos = nextX;
    Ypos = nextY;
    } else {
      Serial.println("ERROR! Distance too big");
      break;
    }
  }
  
  // nudge to the exact final desired coord to correct for rounding down errors
  if ((abs(newX-Xpos)<6) && (abs(newY-Ypos)<6)) { //trap crazy values
      if ((newX-Xpos) >  0)  stepperX->step((newX - Xpos), FORWARD, SINGLE); 
      if ((newX-Xpos) < 0)  stepperX->step((Xpos - newX), BACKWARD, SINGLE);
      if ((newY-Ypos) >  0)  stepperY->step((newY - Ypos), FORWARD, SINGLE); 
      if ((newY-Ypos) < 0)  stepperY->step((Ypos - newY), BACKWARD, SINGLE);
  }
    //update the machine current position
    Xpos = newX;
    Ypos = newY;
}

void zMove (signed long int newZ) {
  //calculates steps to move, and moves, to new Z position requested
  signed long int distance = 0;
  signed long int oldZ = Zpos;

  distance = newZ - oldZ;
  
  if ((distance < 8000) && (distance > -8000)) {
    if (distance >=  1)  stepperZ->step(distance, FORWARD, SINGLE); 
    if (distance <= -1)  stepperZ->step(-1*distance, BACKWARD, SINGLE);
  } else {
    //movement requested is large and likely an error
    Serial.println("ERROR - Z axis range");
  }

  Zpos = newZ;  //update machine current position
}

Fired up the Java GUI and started clicking buttons.  It works!  All the buttons are doing the right things.  Immediately noticed I needed to make some GUI changes though.

-Stop button works fine, but once I've done a release I need to have a function for re powering the motors to recover.   Using the I; command for that;
-Tool speed scale is way too low, need to bump it up -
-Tool control, I didn't put on a direction button for forward/reverse - Done
-I need to look for and obey the busy flags from the arduino - Done, and made a indicator light

Things are working with the Java GUI, the busy light comes on, all the motors move the way I want.  I'm in business.   Here is what it looks like now.  It has gotten a bit busy.



Next step is to figure out what to do on the calibration page.
The way this should work is this:

  • Start with a calibrated, precisely measured square printed on paper.
  • Manually move the XY to a the lower left position
  • Hit a button to say it is there
  • Manually move the XY to the upper right position
  • Enter the size of the square and hit a button to say it is there
  • Lower the tool down to touch the work
  • Hit a button to say it is at 0
  • Lift it and put it down on an object of known thickness
  • Enter the height and hit a button to say it is there
  • Now the encoders and counters can calculate the size of their steps in inches/mm.
  • The units should be changed from counts to inches in the display
Here is the calibration GUI.  Added code to calculate the calibration and reset counters in the Arduinos as needed.

Next step is to go back and modify the GUI I previously made for Manual stepping to report values in the calibrated units that calibration measured or in raw steps as it does now.  Easy added the multiply to the calculated position display.

Alright... now off to figure out how to read and write graphics files.   Basic communication and control all seem to be working well with the mockup machine.


I posted the code for all the parts at
https://code.google.com/p/arduino-java-xyzcnc/downloads/list
This is a working version for manual movement of the machines, haven't implemented the file reading and transferring part yet.

'm rewriting my Java PC program.  The task remaining is to add the file reading and translation code to my communication and control software.  I need to be able to pick up some common format CNC files and execute them.

I want to leverage as much as I can of existing software, and just write a simple line by line translator to take an existing file format and convert it to the commands i need to send to the Arduino.

I used HPGL last time, but it is a dead language and is no longer supported by many tools and graphics editors.   It will be really hard to convert pictures into HPGL.  I need something new.

Researching 3D and CNC file formats... Again

Lots of random scribbling as I google....

http://answers.yahoo.com/question/index?qid=20110530081859AASSPLv
.stl – STL is a file format native to the stereolithography CAD software created by 3D Systems.
.iges – The Initial Graphics Exchange Specification (IGES) (pronounced eye-jess) defines a neutral data format that allows the digital exchange of information among Computer-aided design (CAD) systems.
.obj – OBJ (or .OBJ) is a geometry definition file format first developed by Wavefront Technologies for its Advanced Visualizer animation package. The file format is open and has been adopted by other 3D graphics application vendors. For the most part it is a universally accepted format.
.3ds – 3DS is one of the file formats used by the Autodesk 3ds Max 3D modeling, animation and rendering software.
Gerber - PC board drawing format

Wood carvers like STL files it seems
DXF shows up too - that is autocad

This site has a lot of STL files
http://www.cnc4free.org/

Tried to read one of the STL files.  It is not in ASCII and GIMP can't open it.  So that may not be the best option for me, to write an STL file interpreter.  Most people are trying to create these files, not read them.

b2G that will convert monochrome .bmp file to stl file

.bmp I have in the past written many programs that read and write .bmp files.  The format is pretty simple XY pixels and intensity.   I could use bmp as and easy option, but something more vectory seems better.

This guy is way ahead of me.   I'd have to switch to Linux to borrow his work.
http://www.securetech-ns.ca/camm-linux.html

G-code is used to run cnc machines at the lowest level.

Some tools for creating gcode http://replicat.org/generators

This software converts Eagle PCB into gcode
http://pcbgcode.org/index.php

cad.py looks like a convertor to gcode

I looked at gcode on wikipedia and it's fairly hairy.  Lots of commands to deal with.

Windows .bmp picture drawing

Starting to think I will write a simple bmp drawer to start with.
I want to get the tool running with something.

bmp files say their size and have rows and columns of pixels.  Easy to read.  I can use gimp to create files of any size, scale them, etc to make them suitable.  I'll start with black and white bmp files.

I need to do some googling to make sure there isn't some tool out there I can borrow.

If I choose to write it myself, the programming is going to go like this:
  1. Read in the bmp file into an array
  2. Find the outer extent on top, left and right
  3. move to the rough center
  4. put the pen down on a black dot.
  5. look for a neighbor that is black and move there
  6. continue until no neighbors are black
  7. lift
  8. find another pixel that is black
  9. move there
  10. repeat until the drawing is done
I have some old skill code that read bmp files, I will pull that out and use it to start.

Exploring other options

Found out vectorizing is called "autotracing".   That gave me another thing to google

There is an "autotracing" program on line. 
http://vectormagic.com/home
But the problem is I would then need to be able to read EPS, SVG or PDF files. Maybe the demo will be enough, but it looks like it costs money.

Found another that looks free and online.  Takes a graphics file and creates vector files.
http://www.autotracer.org/

  • Tried it, and EPS output looks like a usable, ASCII vector file.  
  • DXF file is ASCII readable as well.  I'm sure this could get hairy, Autocad is pretty complex.  But this file looks manageable.
  • SVG looks good too.  Seems to be a human readable graphics file that I could interpret
http://en.wikipedia.org/wiki/Scalable_Vector_Graphics
SVG could get out of hand, with lots of shapes and fonts to support.  However the basic file looks good
<path style="fill:#545a64; stroke:none;" d="M30 3L30 10C31.0595 7.46568 31.0595 5.53432 30 3z"/>


This means Move to 30:3, Line to 30:10, Bezier Curve to a list of coords, z=end.

Here is the spec on the SVG file format
http://www.w3.org/TR/2011/REC-SVG11-20110816/paths.html#PathDataMovetoCommands

Played with http://www.autotracer.org/ and I'm pretty happy with the files it produces.  It is really for 2D drawing but it makes simple vector files that are easy to turn into tool control files.  Gimp can read and display them.   Looks like a good quickie option to get off the ground.

This is a little graphic with some letters and a square:

<?xml version="1.0" standalone="yes"?>
<svg width="161" height="150">
<path style="fill:#ffffff; stroke:none;" d="M0 0L0 150L161 150L161 0L0 0z"/>
<path style="fill:#010101; stroke:none;" d="M1 1L1 109L117 109L117 1L1 1z"/>
<path style="fill:#ffffff; stroke:none;" d="M3 3L3 107L115 107L115 85C87.676 91.5053 87.5147 47.4563 115 54L115 3L3 3z"/>
<path style="fill:#010101; stroke:none;" d="M41 43L41 85L49 85L49 69C54.56 69 61.8856 70.2342 66.9568 67.5432C74.8268 63.3671 75.3453 49.6136 67.956 44.7423C61.5192 40.4989 48.4628 43 41 43M80 43L80 50L88 50L88 43L80 43z"/>
<path style="fill:#ffffff; stroke:none;" d="M49 50L49 62C66.8383 61.9882 66.8383 50.0118 49 50z"/>
<path style="fill:#010101; stroke:none;" d="M80 54L80 85L88 85L88 54L80 54z"/>
<path style="fill:#ffffff; stroke:none;" d="M107.043 59.9707C98.9687 61.792 101.299 81.8813 109.794 79.1489C117.684 76.611 116.364 57.8683 107.043 59.9707z"/>
</svg>

I will need to implement the Curve function in the Arduino, and convert the control letters to match just to make life easier.

Also found this code for running in a browser:
https://code.google.com/p/svg-edit/

It has a demo which is a fully functional svg editor that produces similar graphics files that can be edited.  Looks like a winner.
http://svg-edit.googlecode.com/svn/branches/2.6/editor/svg-editor.html


At this point it seems appropriate to start other posts with this topic, this post is getting too long and covers too much.

98 comments:

  1. I'm watching this blog with baited breath.

    If your encoder could be used first with a probe to map the flatness of a pcb, and then adjust the Z axis Gcode accordingly, you might have designed the best PCB mill yet!

    ReplyDelete
  2. This is really interesting and I`m tempted to try it out. In fact ... YES I`m going to try it out ! Thanks for the information. GOOD JOB !

    ReplyDelete
  3. So sorry that you have changed to the new V2motor shields. They are surface mount so I can't really make one myself. I've been following your progress for a while and was hoping that the control java for your sketches would be finessed and usable one day!

    ReplyDelete
  4. Have you even considered RAMPS or GeeTech Rumba for this project?
    All on one Arduino-driven and managed PCB (up to 6 motors, 2 extrusers, DC and AC motors, endstops, misters, air/fans etc. $50-100.00 - AND they use really cheap 8825 or 4988 step-motor-drive plug in modules ($1.80ea)
    http://www.aliexpress.com/wholesale?catId=0&initiative_id=SB_20150821102711&SearchText=rumba

    ReplyDelete
  5. You can guide build 3D model using 3D software and make plates of manchine.

    ReplyDelete
  6. The information you provided was very useful about cnc 3d printer. I will visit again in the near future.

    polished sapphire wafers

    ReplyDelete
  7. This information was very helpful for me and everyone because you have described point to point about it.

    lapping and polishing

    ReplyDelete
  8. I’m very impressed by your work and will recommend your tips to everyone in my circle so that they can also benefit from this information.

    Wafer polishing service

    ReplyDelete
  9. Thank you very much for sharing this useful information. I was doing a project and for that I was looking for related information. Some of the points are very useful. Do share some more material if you have.
    glass cnc machining

    ReplyDelete
  10. Articulately written and well figured out.
    buckets

    ReplyDelete
  11. However you can use the speed ratings to make some judgement of performance differences between brands and models.3d pen

    ReplyDelete
  12. For this situation, an information sheet with a straightforward letterhead or logo will be compelling. By keeping the outline straightforward, the format can be kept on document and the information sheet might be refreshed as required. This will counteract costly reprints at a printer.prototypy

    ReplyDelete
  13. The fundamental preferences of cushion printing when contrasted with different strategies, is a cushion printing machine's interesting capacity to print on sporadic shapes.invoice pad printing

    ReplyDelete
  14. The best printers likewise have USB ports and memory card spaces for simple utilization.best 3d printer under 500

    ReplyDelete
  15. Normally the most well known arrangements fuse inkjet or laser printers that put in color or toner on to a large number of substrates like paper, photograph paper, canvas, glass, metal, marble and others.post card printing

    ReplyDelete
  16. I haven’t any word to appreciate this post.....Really i am impressed from this post....the person who create this post it was a great human..thanks for shared this with us. best 3d printer under 500

    ReplyDelete
  17. I was unaware of the facts you mentioned in your article. It is so helpful that I am sure everyone will praise you for sharing this information. Wonderful work.
    slag removal equipment

    ReplyDelete
  18. This article will discuss various technical aspects and other factors to consider while buying a printer.best cheap 3d printer

    ReplyDelete
  19. Awesome article, it was exceptionally helpful! I simply began in this and I'm becoming more acquainted with it better! Cheers, keep doing awesome!Best Monoprice 3d Printers

    ReplyDelete
  20. Your Quality Inkjet Refills cartridge is a small marvel of technology, it is used in many home printers such as the DeskJet and BubbleJet printers and is the one item that must be in top shape to guarantee efficient, high quality printing.

    ReplyDelete
  21. I can continue perusing this blog like until the end of time.
    3d printer for miniatures

    ReplyDelete
  22. They are, notwithstanding, increasingly constrained in the range and size of printing media that can be utilized - for the most part letter-estimate paper or littler. www.tech-wonders.com

    ReplyDelete
  23. Some truly wonderful work on behalf of the owner of this internet site , perfectly great articles . T-Shirt Printer

    ReplyDelete
  24. I’m excited to uncover this page. I need to to thank you for ones time for this particularly fantastic read!! I definitely really liked every part of it and i also have you saved to fav to look at new information in your site. Authorized Dealer for Develop & Espon

    ReplyDelete
  25. It is not every day that I have the possibility to see something like this.. Application and Web development

    ReplyDelete
  26. This comment has been removed by the author.

    ReplyDelete
  27. On the off chance that we return to our discussion on information (sorry, I can't support myself) we can utilize that information to make an individualized, customized, important printed promoting guarantee that is diverse for every one of your beneficiaries.Fine art printing NYC

    ReplyDelete
  28. This comment has been removed by the author.

    ReplyDelete
  29. This comment has been removed by the author.

    ReplyDelete
  30. Useful article which can also help the begineer who are new to the
    3d printing industry.

    ReplyDelete
  31. Positive site, where did u come up with the information on this posting? I'm pleased I discovered it though, ill be checking back soon to find out what additional posts you include. Frames STL models for CNC

    ReplyDelete
  32. This comment has been removed by the author.

    ReplyDelete
  33. This comment has been removed by the author.

    ReplyDelete
  34. This comment has been removed by the author.

    ReplyDelete
  35. This comment has been removed by the author.

    ReplyDelete
  36. On use of 3D printing technology points you mentioned are very interesting. I admire your part of work, regards for all the worthwhile articles. hp officejet 4650 installation

    ReplyDelete
  37. It is truly a well-researched content and excellent wording. I got so engaged in this material that I couldn’t wait reading. I am impressed with your work and skill. Thanks. copier Orlando

    ReplyDelete
  38. Manufacturers of non-woven shopping bags use automatic machines roxwellwaterhouse.com the production. The process entails different types of machines that are being used.

    ReplyDelete
  39. This comment has been removed by the author.

    ReplyDelete
  40. This is a great inspiring article.I am pretty much pleased with your good work.You put really very helpful information. Keep it up. Keep blogging. Looking to reading your next post. הדפסה על כוסות קפה

    ReplyDelete
  41. Thank you because you have been willing to share information with us. we will always appreciate all you have done here because I know you are very concerned with our. cheap color copy at 55printing

    ReplyDelete
  42. This is something I was searching for many days. My thirst has been quenched now after reading your article. I am highly thankful to you for writing this article.
    optical polishing service

    ReplyDelete
  43. A layered site design can enable an organization to respond all the more rapidly and viably. CAD modeling with SolidWorks

    ReplyDelete
  44. I wouldn't fret putting resources into my business, as long as the apparatus I am putting resources into spares personal time and carries out the responsibility. To personal, time truly is cash. SEO PACKAGE

    ReplyDelete
  45. If it's not too much trouble attempt these five checks first before you murder your printer. Custom Macaron Boxes

    ReplyDelete
  46. which is an issue in case you're advertising another person's item through Clickbank, Commission Junction, ModernClick, or any of the other associate systems.Backlinks service

    ReplyDelete
  47. Printers are essential peripherals, performing a critical role as they render electronic information into tangible records or material output.long range walkie talkies

    ReplyDelete
  48. You must participate in a contest for probably the greatest blogs on the web. I will recommend this web site! how much does solidworks software cost

    ReplyDelete
  49. Impressive web site, Distinguished feedback that I can tackle. Im moving forward and may apply to my current job as a pet sitter, which is very enjoyable, but I need to additional expand. Regards. Markforged 3D Printers

    ReplyDelete
  50. Simply a smiling visitor here to share the love (:, btw great style and design . cheapsoft4you.com

    ReplyDelete
  51. It proved to be Very helpful to me and I am sure to all the commentators here! printer manufacturers

    ReplyDelete
  52. Among one of the finest pieces on the web, awakening in its specificity.
    square canvas prints

    ReplyDelete
  53. I'm glad I found this web site, I couldn't find any knowledge on this matter prior to.Also operate a site and if you are ever interested in doing some visitor writing for me if possible feel free to let me know, im always look for people to check out my web site. stainless steel

    ReplyDelete
  54. I am thankful to you because your article is very helpful for me to carry on with my research in the same area. Your quoted examples are relevant to my research as well.

    laser slag

    ReplyDelete
  55. While the previous data leakage scenarios have been accidental in nature, data remaining on printers could be the target of an educated attacker, one that understands the value of data residing on printers and who has the ability to compromise that data.buy photocopier

    ReplyDelete
  56. As an expert, it doesn't bode well to record and convey a lot of information on a plate if the mark doesn't satisfy the estimation of the substance. Printing

    ReplyDelete
  57. You need to be a part of a tournament for just one of the finest blogs on-line. I most certainly will suggest this website! custom machining services

    ReplyDelete
  58. Positive site, where did u come up with the information on this posting? I'm pleased I discovered it though, ill be checking back soon to find out what additional posts you include. 3dmodelmania.com - site with STL models for cnc

    ReplyDelete
  59. I truly appreciate this post. I have been looking all over for this! Thank goodness I found it on Bing. You have made my day! Thx again pneumatic linear actuator

    ReplyDelete
  60. This particular is usually apparently essential and moreover outstanding truth along with for sure fair-minded and moreover admittedly useful My business is looking to find in advance designed for this specific useful stuffs… Best 3d printers under $500

    ReplyDelete
  61. Quickly underneath that the printer is distinguished. On the off chance that the name of this gadget isn't the printer you are hoping to use, there is a triangle pointing down on the right-hand side. printers for graphic designers

    ReplyDelete
  62. So where are you in this spectrum? Make the best and most objective assessment of your business that you can - it will provide best starting point for how often you should replace your copy machine. photocopy machine

    ReplyDelete
  63. When you work in a office there you can see many trade copier services but it is very rare that they are using smart printers. Because they thought that the price of those printers are so much but they are totally wrong. Right now from many place you could buy those printers just at a reasonable price.

    ReplyDelete
  64. Wow, cool post. I'd like to write like this too - taking time and real hard work to make a great article... but I put things off too much and never seem to get started. Thanks though. 3D STL models

    ReplyDelete
  65. Thankses, I love my animals, you helped me much with taking care of him. 먹튀검증

    ReplyDelete
  66. Have you tried twitterfeed on your blog, i think it would be cool.:**-; https://royalcbd.com/product/cbd-oil-500mg/

    ReplyDelete
  67. I really appreciate this wonderful post that you have provided for us. I assure this would be beneficial for most of the people. read more,

    ReplyDelete
  68. This is such a great resource that you are providing and you give it away for free. I love seeing blog that understand the value of providing a quality resource for free. his link

    ReplyDelete
  69. For instance, generate suppliers have worked with the Meals & Drug Administration (FDA) to make confident that the industry requirements created outcome in the most effective and most successful processes. And to communicate any public announcements, the sector has formulated relationships with associations like the Nationwide Grocer’s Association (NGA). 먹튀폴리스

    ReplyDelete
  70. Thanks for a very interesting blog. What else may I get that kind of info written in such a perfect approach? I’ve a undertaking that I am simply now operating on, and I have been at the look out for such info. temperature chamber

    ReplyDelete
  71. I found so many interesting stuff in your blog especially its discussion. From the tons of comments on your articles, I guess I am not the only one having all the enjoyment here! keep up the good work... flat screen printing machine

    ReplyDelete
  72. Pretty good post. I just stumbled upon your blog and wanted to say that I have really enjoyed reading your blog posts. Any way I'll be subscribing to your feed and I hope you post again soon. Big thanks for the useful info. two shot injection mold

    ReplyDelete
  73. Luckily, there are numerous innovative ways a business can advance itself on a careful spending plan, for example, by utilizing uniquely printed boxes.Custom Printed Boxes

    ReplyDelete
  74. Loved to read your blog. I like the significant data you give in your articles. I am impressed the manner in which you introduced your perspectives and appreciating the time and exertion you put into your blog. Much thanks to you such a great amount for sharing this sort of information.
    Visit us for Customized Round Badges

    ReplyDelete
  75. Rendervision normally requires vast measure of imaginative authority over what may show up in the scene.

    ReplyDelete
  76. Quickly this particular web address may irrefutably wind up renowned between each and every composing lots of people, due to persistent content articles as well as evaluations as well as rankings. office supplies north haven ct

    ReplyDelete
  77. On my website you'll see similar texts, write what you think. Reverse Engineering

    ReplyDelete
  78. Thank you because you have been willing to share information with us. we will always appreciate all you have done here because I know you are very concerned with our. best hard tonneau cover f150

    ReplyDelete
  79. Took me time to read all the comments, but I really enjoyed the article. It proved to be Very helpful to me and I am sure to all the commenters here! It’s always nice when you can not only be informed, but also entertained! best all in one printer

    ReplyDelete
  80. Aw, i thought this was quite a good post. In concept I would like to devote writing such as this moreover – spending time and actual effort to produce a great article… but exactly what do I say… I procrastinate alot by no means manage to get something done. 파워사다리

    \
    ----------------------------------


    This design is spectacular! You definitely know how to keep a reader amused. Between your wit and your videos, I was almost moved to start my own blog (well, almost…HaHa!) Great job. I really enjoyed what you had to say, and more than that, how you presented it. Too cool! 먹튀검증

    ReplyDelete
  81. What would be your next topic next week on your blog.*”~~- 먹튀

    ReplyDelete
  82. One Tree Hill should be the best TV series for me, the characters are great~ 먹튀

    ReplyDelete
  83. Really I enjoy your site with effective and useful information. It is included very nice post with a lot of our resources.thanks for share. i enjoy this post. miniature 3d printing

    ReplyDelete
  84. I found so many interesting stuff in your blog especially its discussion. From the tons of comments on your articles, I guess I am not the only one having all the enjoyment here! keep up the good work... 3d printing resin

    ReplyDelete
  85. Yes i am totally agreed with this article and i just want say that this article is very nice and very informative article.I will make sure to be reading your blog more. You made a good point but I can't help but wonder, what about the other side? !!!!!!Thanks cnc electronic components

    ReplyDelete
  86. This particular is usually apparently essential and moreover outstanding truth along with for sure fair-minded and moreover admittedly useful My business is looking to find in advance designed for this specific useful stuffs… toner buyer

    ReplyDelete
  87. You have to read printer reviews, identify your specific needs, and weigh your best options. Only through these steps can you select the best picture printer in the market. all in one printer reviews

    ReplyDelete
  88. Amazing post thanks for sharing with us it is very informative.
    popcorn Boxes
    Pencil Boxes

    ReplyDelete
  89. If you are settling for color printing or be it black and white, you should take all considerations in choosing and purchasing your printers. But choosing a printer is really a hard task. Imagine you... all in one printer reviews

    ReplyDelete
  90. I don’t have time right now to write a lot, but when I returned tomorrow evening, I will explain in depth why I disagree with this article. preventive maintenance

    ReplyDelete
  91. Thanks for helping us understand this topic. You have written it in a way that makes it very simple to understand. Thank you so much. Edge Rounding Deburring Machines.

    ReplyDelete
  92. Outstanding post, I think people should learn a lot from this web site its rattling user genial . Maijin metal

    ReplyDelete
  93. With havin so much written content do you ever run into any problems of plagorism or copyright infringement? My site has a lot of exclusive content I’ve either written myself or outsourced but it looks like a lot of it is popping it up all over the web without my permission. Do you know any techniques to help prevent content from being ripped off? I’d really appreciate it. cnc machining

    ReplyDelete
  94. I got what you intend,bookmarked , very decent internet site . cnc machining parts

    ReplyDelete
  95. You can find a printer to rent for almost anything that you could need. renting impresoras madrid

    ReplyDelete
  96. A 16 port PoE switch can connect up to 16 devices such as IP cameras, wireless access points, and VoIP phones. Best 16 Port PoE Switch

    ReplyDelete
  97. The way you balance poise and passion in your content is remarkable, infusing each topic with life and making it resonate on a personal level.

    grey marble in Ras AL Khaimah

    ReplyDelete