MarlinFirmware / Marlin

Marlin is an optimized firmware for RepRap 3D printers based on the Arduino platform. Many commercial 3D printers come with Marlin installed. Check with your vendor if you need source code for your specific machine.
https://marlinfw.org
GNU General Public License v3.0
16.24k stars 19.22k forks source link

[FR] Polar kinematics for vertical CNC or plotter #5275

Closed tilkor closed 3 years ago

tilkor commented 7 years ago

Hello,

I came across an open source palorgraph/Vertical plotter style CNC mill/router (http://www.maslowcnc.com/home/) that I thought would be a nice feature to add to marlin. This style of CNC looks like it would be very easy to DIY build using a Ramps 1.4 board.

I found Rick McConny's Pen Plotter based off Marlin (https://github.com/RickMcConney/PenPlotter/tree/master/Marlin) that is the same concept just with the Z axis limited.

I am not a coder by any means so I really do not have a clue how hard it would be to add this feature in but I would greatly appreciate it if it was taken into consideration.

Thanks,

-Maslowcnc souce code (https://github.com/MaslowCNC/Firmware/tree/master/cnc_ctrl_v1)

tilkor commented 7 years ago

I have based the code off McConny's work.

Configuration.h:

// Uncomment the following line to enable POLARGRAPH_CNC kinematics
#define POLARGRAPH_CNC

Marlin.h:

#ifdef POLARGRAPH_CNC
  extern float polarGRAPH_CNC_HomeY;
  extern float polarGRAPH_CNC_HomeZ;
  extern float polarGRAPH_CNC_PageWidth;
  extern float polarGRAPH_CNC_LastA;
  extern float polarGRAPH_CNC_LastB;
  extern float polarGRAPH_CNC_LastC;
#endif

Planner.cpp:

#ifdef POLARGRAPH_CNC

    float aPos = sqrt(x*x + y*y);
    float bPos = sqrt(sq(POLARGRAPH_CNC_PageWidth - x) + y*y);
    float cPos = z
    /*
    SERIAL_PROTOCOLPGM("X:");
    SERIAL_PROTOCOL(x);
    SERIAL_PROTOCOLPGM(" Y:");
    SERIAL_PROTOCOL(y);
    SERIAL_PROTOCOLPGM(" Z:");
    SERIAL_PROTOCOL(z);

    SERIAL_PROTOCOLPGM(" A:");
    SERIAL_PROTOCOL(aPos);
    SERIAL_PROTOCOLPGM(" B:");
    SERIAL_PROTOCOL(bPos);
    SERIAL_PROTOCOLPGM(" C:");
    SERIAL_PROTOCOL(cPos);

    SERIAL_PROTOCOLPGM(" LA:");
    SERIAL_PROTOCOL(POLARGRAPH_CNC_LastA);
    SERIAL_PROTOCOLPGM(" LB:");
    SERIAL_PROTOCOL(POLARGRAPH_CNC_LastB);
    SERIAL_PROTOCOLPGM(" LC:");
    SERIAL_PROTOCOL(POLARGRAPH_CNC_LastC);
    */

    target[X_AXIS] = lround((aPos - POLARGRAPH_CNC_LastA)*axis_steps_per_unit[X_AXIS]);
    target[Y_AXIS] = lround((bPos - POLARGRAPH_CNC_LastB)*axis_steps_per_unit[Y_AXIS]);
    target[Z_AXIS] = lround((cPos - POLARGRAPH_CNC_LastC)*axis_steps_per_unit[Z_AXIS]);
    position[X_AXIS] = 0;
    position[Y_AXIS] = 0;
    position[Z_AXIS] = 0;
    POLARGRAPH_CNC_LastA = aPos;
    POLARGRAPH_CNC_LastB = bPos;
    POLARGRAPH_CNC_LastC = cPos;
    /*
    SERIAL_PROTOCOLPGM(" TX:");
    SERIAL_PROTOCOL(target[X_AXIS]);
    SERIAL_PROTOCOLPGM(" TY:");
    SERIAL_PROTOCOL(target[Y_AXIS]);
    SERIAL_PROTOCOLPGM(" TZ:");
    SERIAL_PROTOCOL(target[Z_AXIS]);
    SERIAL_PROTOCOL("\n");
    */

#endif

Marlin_main.cpp:

#ifdef POLARGRAPH_CNC
float POLARGRAPH_CNC_HomeY = 250;
float POLARGRAPH_CNC_HomeZ = 0;
float POLARGRAPH_CNC_PageWidth = 840;
float POLARGRAPH_CNC_LastA;
float POLARGRAPH_CNC_LastB;
float POLARGRAPH_CNC_LastC;

void POLARGRAPH_CNC_Home(float homeY, float homeZ) {
    POLARGRAPH_CNC_HomeY = homeY;
    POLARGRAPH_CNC_HomeZ = homeZ;
    float x = POLARGRAPH_CNC_PageWidth / 2;
    float y = POLARGRAPH_CNC_HomeY;
    float z = POLARGRAPH_CNC_HomeZ;
    current_position[X_AXIS] = x;
    current_position[Y_AXIS] = y;
    current_position[Z_AXIS] = z;

    float aPos = sqrt(x*x + y*y);
    float bPos = sqrt(sq(POLARGRAPH_CNC_PageWidth - x) + y*y);
    float cPos = z
    long a = lround(aPos*axis_steps_per_unit[X_AXIS]);
    long b = lround(bPos*axis_steps_per_unit[Y_AXIS]);
    long c = lround(cPos*axis_steps_per_unit[Z_AXIS]);

    st_set_position(a, b, c, 0);
    POLARGRAPH_CNC_LastA = aPos;
    POLARGRAPH_CNC_LastB = bPos;
    POLARGRAPH_CNC_LastC = cPos;
}

void POLARGRAPH_CNC_Width(float value) {
    POLARGRAPH_CNC_PageWidth = value;
    POLARGRAPH_CNC_Home(POLARGRAPH_CNC_HomeY);
    POLARGRAPH_CNC_Home(POLARGRAPH_CNC_HomeZ);
}

void POLARGRAPH_CNC_SegmentLine(float x2, float y2, float z2) {
    float x1 = current_position[X_AXIS];
    float y1 = current_position[Y_AXIS];
    float z1 = current_position[Z_AXIS];
    float dx = x2 - x1;
    float dy = y2 - y1;
    float dz = z2 - z1;
    float x = x1;
    float y = y1;
    float z = z1;
    float steps = fabs(dx);
    if (fabs(dy) > steps)
        steps = fabs(dy);
    dx = dx / steps;
    dy = dy / steps;
    dz = dz / steps;
    // Com::print("seg line ");
    // Com::print((int)steps);
    // Com::print("\n ");
    for (int s = 0; s<steps - 1; s++) {
        x += dx;
        y += dy;
        z += dz;
        destination[X_AXIS] = x;
        destination[Y_AXIS] = y;
        destination[Z_AXIS] = z;
        prepare_move();
    }
    destination[X_AXIS] = x2;
    destination[Y_AXIS] = y2;
    destination[Z_AXIS] = z2;
    prepare_move();
}
#endif

#ifdef POLARGRAPH_CNC
  polarGRAPH_CNC_Home(250);
#endif

#ifdef POLARGRAPH_CNC
            polarGRAPH_CNC_SegmentLine(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS]);
        #else
            prepare_move();
        #endif

#ifdef POLARGRAPH_CNC
   case 1: {
       if (code_seen('Y')) {
           POLARGRAPH_CNC_Home(code_value());
       }
   }
   case 4: {
       if (code_seen('X')) {
           POLARGRAPH_CNC_Width(code_value());
       }
   }
#endif

#ifdef POLARGRAPH_CNC

  plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate*feedmultiply / 60 / 100.0, active_extruder);

#endif

#if ! (defined DELTA || defined SCARA || defined POLAR || defined POLARGRAPH_CNC)
  // Do not use feedmultiply for E or Z only moves
  if( (current_position[X_AXIS] == destination [X_AXIS]) && (current_position[Y_AXIS] == destination [Y_AXIS])) {
      plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate/60, active_extruder);
  }
  else {
    plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate*feedmultiply/60/100.0, active_extruder);
  }
#endif // !(DELTA || SCARA || POLARGRAPH_CNC)
thinkyhead commented 7 years ago

@tilkor Some context seems to be missing in the last set of code for Marlin_main.cpp. Where is the code following the POLARGRAPH_CNC_SegmentLine function meant to go? Where is the code for Planner.cpp meant to go?

Also, is the case 1 supposed to fall through into case 4, or is a break statement needed?

thinkyhead commented 7 years ago

Here is the code as it would need to be updated, style-wise, for the latest Marlin codebase:

Configuration.h:

// Uncomment the following line to enable POLARGRAPH_CNC kinematics
#define POLARGRAPH_CNC

Conditionals_post.h:

#define IS_KINEMATIC (ENABLED(DELTA) || IS_SCARA || ENABLED(POLAR) || ENABLED(POLARGRAPH_CNC))

Marlin.h:

#if ENABLED(POLARGRAPH_CNC)
  extern float polarGRAPH_CNC_PageWidth,
               polarGRAPH_CNC_LastA,
               polarGRAPH_CNC_LastB,
               polarGRAPH_CNC_LastC;
#endif

Planner.cpp:

#if ENABLED(POLARGRAPH_CNC)

  const float aPos = HYPOT(x, y),
              bPos = HYPOT(POLARGRAPH_CNC_PageWidth - x, y),
              cPos = z;
  /*
  SERIAL_PROTOCOLPGM("X:");
  SERIAL_PROTOCOL(x);
  SERIAL_PROTOCOLPGM(" Y:");
  SERIAL_PROTOCOL(y);
  SERIAL_PROTOCOLPGM(" Z:");
  SERIAL_PROTOCOL(z);

  SERIAL_PROTOCOLPGM(" A:");
  SERIAL_PROTOCOL(aPos);
  SERIAL_PROTOCOLPGM(" B:");
  SERIAL_PROTOCOL(bPos);
  SERIAL_PROTOCOLPGM(" C:");
  SERIAL_PROTOCOL(cPos);

  SERIAL_PROTOCOLPGM(" LA:");
  SERIAL_PROTOCOL(POLARGRAPH_CNC_LastA);
  SERIAL_PROTOCOLPGM(" LB:");
  SERIAL_PROTOCOL(POLARGRAPH_CNC_LastB);
  SERIAL_PROTOCOLPGM(" LC:");
  SERIAL_PROTOCOL(POLARGRAPH_CNC_LastC);
  */

  target[X_AXIS] = lround((aPos - POLARGRAPH_CNC_LastA) * axis_steps_per_unit[X_AXIS]);
  target[Y_AXIS] = lround((bPos - POLARGRAPH_CNC_LastB) * axis_steps_per_unit[Y_AXIS]);
  target[Z_AXIS] = lround((cPos - POLARGRAPH_CNC_LastC) * axis_steps_per_unit[Z_AXIS]);
  position[X_AXIS] = position[Y_AXIS] = position[Z_AXIS] = 0;
  POLARGRAPH_CNC_LastA = aPos;
  POLARGRAPH_CNC_LastB = bPos;
  POLARGRAPH_CNC_LastC = cPos;
  /*
  SERIAL_PROTOCOLPGM(" TX:");
  SERIAL_PROTOCOL(target[X_AXIS]);
  SERIAL_PROTOCOLPGM(" TY:");
  SERIAL_PROTOCOL(target[Y_AXIS]);
  SERIAL_PROTOCOLPGM(" TZ:");
  SERIAL_PROTOCOL(target[Z_AXIS]);
  SERIAL_PROTOCOL("\n");
  */

#endif

Marlin_main.cpp:

#if ENABLED(POLARGRAPH_CNC)
  float POLARGRAPH_CNC_HomeY = 250,
        POLARGRAPH_CNC_HomeZ = 0,
        POLARGRAPH_CNC_PageWidth = 840,
        POLARGRAPH_CNC_LastA,
        POLARGRAPH_CNC_LastB,
        POLARGRAPH_CNC_LastC;

  void POLARGRAPH_CNC_Home(const float &homeY, const float &homeZ) {
    POLARGRAPH_CNC_HomeY = homeY;
    POLARGRAPH_CNC_HomeZ = homeZ;
    const float x = POLARGRAPH_CNC_PageWidth * 0.5,
                y = POLARGRAPH_CNC_HomeY,
                z = POLARGRAPH_CNC_HomeZ;
    current_position[X_AXIS] = x;
    current_position[Y_AXIS] = y;
    current_position[Z_AXIS] = z;

    float aPos = HYPOT(x, y),
          bPos = HYPOT(POLARGRAPH_CNC_PageWidth - x, y),
          cPos = z;
    long a = lround(aPos * axis_steps_per_unit[X_AXIS]),
         b = lround(bPos * axis_steps_per_unit[Y_AXIS]),
         c = lround(cPos * axis_steps_per_unit[Z_AXIS]);

    stepper.set_position(a, b, c, 0);
    POLARGRAPH_CNC_LastA = aPos;
    POLARGRAPH_CNC_LastB = bPos;
    POLARGRAPH_CNC_LastC = cPos;
  }

  void POLARGRAPH_CNC_Width(const float &value) {
    POLARGRAPH_CNC_PageWidth = value;
    POLARGRAPH_CNC_Home(POLARGRAPH_CNC_HomeY);
    POLARGRAPH_CNC_Home(POLARGRAPH_CNC_HomeZ);
  }

  void POLARGRAPH_CNC_SegmentLine(const float &x2, const float &y2, const float &z2) {
    const float x1 = current_position[X_AXIS],
                y1 = current_position[Y_AXIS],
                z1 = current_position[Z_AXIS];
    float dx = x2 - x1, dy = y2 - y1, dz = z2 - z1, x = x1, y = y1, z = z1;
    const float steps = fabs(dx);
    NOLESS(steps, fabs(dy));
    dx /= steps;
    dy /= steps;
    dz /= steps;
    // Com::print("seg line ");
    // Com::print((int)steps);
    // Com::print("\n ");
    for (int s = 0; s < steps - 1; s++) {
      x += dx;
      y += dy;
      z += dz;
      destination[X_AXIS] = x;
      destination[Y_AXIS] = y;
      destination[Z_AXIS] = z;
      prepare_move_to_destination();
    }
    destination[X_AXIS] = x2;
    destination[Y_AXIS] = y2;
    destination[Z_AXIS] = z2;
    prepare_move_to_destination();
  }

#endif // POLARGRAPH_CNC

#if ENABLED(POLARGRAPH_CNC)
  polarGRAPH_CNC_Home(250);
#endif

#if ENABLED(POLARGRAPH_CNC)
  polarGRAPH_CNC_SegmentLine(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS]);
#else
  prepare_move_to_destination();
#endif

#if ENABLED(POLARGRAPH_CNC)
  case 1: { if (code_seen('Y')) POLARGRAPH_CNC_Home(code_value()); }
  case 4: { if (code_seen('X')) POLARGRAPH_CNC_Width(code_value()); }
#endif

#if ENABLED(POLARGRAPH_CNC)

  planner.buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], MMS_SCALED(feedrate), active_extruder);

#endif

#if !IS_KINEMATIC
  // Do not use feedmultiply for E or Z only moves
  if (current_position[X_AXIS] == destination[X_AXIS] && current_position[Y_AXIS] == destination[Y_AXIS])
    planner.buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], MMM_TO_MMS(feedrate), active_extruder);
  else
    planner.buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], MMS_SCALED(feedrate), active_extruder);
#endif // !(DELTA || SCARA || POLARGRAPH_CNC)
tilkor commented 7 years ago

I hope this helps.

Planner.cpp code goes under, float junction_deviation = 0.1; I have it starting at line 563.

Marlin_main.cpp code

Routines, starting at line 440

#ifdef POLARGRAPH_CNC
float POLARGRAPH_CNC_HomeY = 250;
float POLARGRAPH_CNC_HomeZ = 0;
float POLARGRAPH_CNC_PageWidth = 840;
float POLARGRAPH_CNC_LastA;
float POLARGRAPH_CNC_LastB;
float POLARGRAPH_CNC_LastC;
.............
destination[Z_AXIS] = z2;
    prepare_move();
}
#endif

Under void process_commands()

void process_commands()
{
  unsigned long codenum; //throw away variable
  char *starpos = NULL;
#ifdef ENABLE_AUTO_BED_LEVELING
  float x_tmp, y_tmp, z_tmp, real_z;
#endif
  if(code_seen('G'))
  {
    switch((int)code_value())
    {
    case 0: // G0 
          if(Stopped == false) {
        get_coordinates(); // For X Y Z E F
          #ifdef FWRETRACT
            if(autoretract_enabled)
            if( !(code_seen('X') || code_seen('Y') || code_seen('Z')) && code_seen('E')) {
              float echange=destination[E_AXIS]-current_position[E_AXIS];
              if((echange<-MIN_RETRACT && !retracted) || (echange>MIN_RETRACT && retracted)) { //move appears to be an attempt to retract or recover
                  current_position[E_AXIS] = destination[E_AXIS]; //hide the slicer-generated retract/recover from calculations
                  plan_set_e_position(current_position[E_AXIS]); //AND from the planner
                  retract(!retracted);
                  return;
              }
            }
          #endif //FWRETRACT

           prepare_move();

        //ClearToSend();
      }
      break;
    case 1: // G1
      if(Stopped == false) {
        get_coordinates(); // For X Y Z E F
          #ifdef FWRETRACT
            if(autoretract_enabled)
            if( !(code_seen('X') || code_seen('Y') || code_seen('Z')) && code_seen('E')) {
              float echange=destination[E_AXIS]-current_position[E_AXIS];
              if((echange<-MIN_RETRACT && !retracted) || (echange>MIN_RETRACT && retracted)) { //move appears to be an attempt to retract or recover
                  current_position[E_AXIS] = destination[E_AXIS]; //hide the slicer-generated retract/recover from calculations
                  plan_set_e_position(current_position[E_AXIS]); //AND from the planner
                  retract(!retracted);
                  return;
              }
            }
          #endif //FWRETRACT

                #ifdef POLARGRAPH_CNC
            polarGRAPH_CNC_SegmentLine(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS]);
        #else
            prepare_move();
        #endif

        //ClearToSend();
      }
      break;
#ifndef SCARA //disable arc support
thinkyhead commented 7 years ago

@tilkor Thanks. I hope some will find this useful. It would be interesting to see polar kinematics integrated into the latest code-base, but obviously that is more involved than merely the handful of changes laid out here. I've recently been working on a SCARA robot for a client, and a myriad of changes were required to support those specific kinematics throughout the code-base. I would be interested to see a video and other details about the specific machine that you are trying to support.

tilkor commented 7 years ago

I greatly appreciate your help in cleaning up the code, I am still working out all the details but this is kind of the design i had in mind.

The main goal is to make it compact so when not in use it would folds up neatly against a wall with hinges along the top and a folding standoff at the bottom. The idea is to be able to cut out CAD designs from a sheet of plywood/steel using a router/plasma cutter attached to bike chains with counter weights through a step motors attached to the red squares.

I want run it all a arduino/ramps setup (super cheap) with 3 nema-17 (two mounted on the back side of the red plates). I would use the Z-axis 3rd nema to control the router's cut depth as it takes passes over the design.

polargraph_cnc build 1

polargraph_cnc build 2

Cromaglious commented 7 years ago

There is some interest in using polar with a linear actuator to be used as a portable plasma cutter. On youtube with user AvE. https://www.youtube.com/watch?v=IllVwt6CRJQ

tilkor commented 7 years ago

I like the idea, I have not had time to work on this project recently.

I have based this mostly on a pen plotter and knowing the distance between the two motors (page width).

fiveangle commented 7 years ago

I was looking for info on MaslowCNC and a google search brought me here - lol

Basically same idea as PenPlotter here, but a 4x8 plywood CNC router.

https://www.youtube.com/watch?v=CIQC5ZyzfDM https://www.youtube.com/watch?v=Q-KCZoxvzcQ

Looks like it runs it's own home-rolled FW:

https://github.com/MaslowCNC/Firmware

Unfortunately, because it uses DC motors with ~8k steps/rotation rotory encoders, it doesn't look like it would be a slam-dunk for porting to Marlin, even if polar kinematics were implemented unless you slap an Arduino running Misan's stepper->servo closed-loop converter software between: https://github.com/misan/dcservo

Come to think of it, once 32-bit Marlin becomes commonplace, I could see Marlin actually handling the closed-loop DC servo translation directly. This would be so awesome for CNC. One can dream... :)

boelle commented 5 years ago

@tilkor

Please post your question either on discord: https://discord.gg/n5NJ59y or on facebook: https://www.facebook.com/groups/2080308602206119/ The issue list is for bugs and feature requests only Please close this issue once you have posted it on one of the 2 links Thanks :-D

boelle commented 5 years ago

@thinkyhead i think we can close this one

again its labeled as a question and there is no activity from the OP

boelle commented 5 years ago

yep we should be able to close this one, no activity for months

fiveangle commented 5 years ago

The issue list is for bugs and feature requests only

This was a feature request for implementing the polar kinematics. Whoever put the question tag on it probably just skimmed it quickly.

phantomse commented 5 years ago

In my search for an hanging all in one V-Plotter. I wanted to make it wireless (Battery or Power-line only, or get its power from the two cables it hangs from), much easier to set up and calibrate no matter of the size of the surface.

ESP3D continues figuring out the wireless part and can be added as library in Marlin. Luc is maintaining a version of Marlin 2.0 with ESP3D integrated.

There is a project which has all steps needed to change in Marlin (old version) for an V-Plotter https://github.com/RickMcConney/PenPlotter Only since Marlin 2.0 there is support for esp32 so there may be some changes.

Hardware: UNO D1 R32 (only one pin seams to be incompatible, snippet it off) + CNC Shield V3.0 LCD + Stepper + Servo + Solenoid + Endstops + SDCard ... => auto color changing and advanced functions all in one, similar to https://hackster.imgix.net/uploads/attachments/536161/img_0131_joZbk8YliZ.JPG?auto=compress%2Cformat&w=1280&h=960&fit=max

thinkyhead commented 5 years ago

I've been preferring Makelangelo for hanging plotter firmware. Marlin won't be integrating plotter or additional CNC or Laser support before version 2.1, but we definitely encourage others to adapt Marlin to special hardware.

thisiskeithb commented 3 years ago

Added in #22790

github-actions[bot] commented 2 years ago

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.