Duet3D Logo

    Duet3D

    • Register
    • Login
    • Search
    • Categories
    • Tags
    • Documentation
    • Order
    1. Home
    2. xyzdims
    • Profile
    • Following 1
    • Followers 3
    • Topics 4
    • Posts 26
    • Best 4
    • Controversial 0
    • Groups 0

    xyzdims

    @xyzdims

    6
    Reputation
    15
    Profile views
    26
    Posts
    3
    Followers
    1
    Following
    Joined Last Online
    Website xyzdims.com/ Location Switzerland

    xyzdims Unfollow Follow

    Best posts made by xyzdims

    • 5-axis CNC/FDM: Tool vs Machine Coordinates (Inverse Kinematics)

      I post this piece in CNC section, as it deals with 5-axis setup but with FDM in mind.

      Issue

      I have a simple 5-axis FDM setup, 3-axis X,Y,Z plus Z-rotation and tilt of the FDM nozzle; and due to the actual setup I need to do an inverse kinematic (IK) calculation. I can add this IK to the firmware of Duet itself, but I also can post-process G-code from tool coordinates to machine coordinates (I just did this as an experiment).

      Advantages:

      • no change in firmware (no recompiling)
      • simple profiling (change setup needs no change in firmware required), each setup/machine has its own profile (a JSON file describing the inv kinematics)
      • simple calibration of actual (physical) offsets and angles

      Disadvantages:

      • machine dependent G-code
      • another step to convert G-code
      • risk of doing stupid things (e.g. running tool into the bed) as firmware doesn't prevent it

      To any experienced CNC machinist and multi-axis wizard, what are your thoughts? I don't know how CNC industry in general deals tool vs machine coords/g-code, and I like to get to know some good practices. Thanks.

      If you like to see the actual setup https://xyzdims.com/2021/02/08/3d-printing-penta-axis-pax-5-axis-printing-option/ (work in progress)

      posted in CNC
      xyzdims
      xyzdims
    • RepRapFirmware Python API

      For a project I required a basic API to connect to RRF boards via HTTP, and wrote a small wrapper (I didn't find anything alike):

      https://github.com/Spiritdude/RepRapFirmwarePyAPI

      import RepRapFirmwareAPI
      
      rrf = RepRapFirmwareAPI.RRFRestAPI("192.168.0.12")
      
      resp = rrf.gcode("M122")              # synchronous
      # Note: type(resp) is either dict containing data, or str 
      
      rrf.gcode("M122","async")             # asynchronous
      resp = rrf.reply()
      
      rrf.upload("test.gcode","/gcodes/test.gcode")
      rrf.print("/gcodes/test.gcode")
      rrf.print_status()
      

      gcode() by default operates synchronous, it waits for a sensible reply, but the behavior can be changed; also, reply() by default is asynchronous, but can be changed as well.

      Note: the API is very experimental, if you adapt/adopt it, please check back the github the next weeks (e.g. until March 2023) to follow changes, e.g. in behavior.

      Any feedback to improve Python API is appreciated.

      @T3P3Tony or @dc42 as of current RRF there is no support for HTTP sessions - so when I send some command via HTTP and poll with /rr_reply, for a long running gcode() command (like heating or homing), the web-GUI running in a browser likely interferes?

      posted in Third-party software
      xyzdims
      xyzdims
    • RE: 5-axis CNC/FDM: Tool vs Machine Coordinates (Inverse Kinematics)

      @Miss-Rebekah @droftarts thanks for the feedback.

      I realize in CNC world the CAM software does the heavy lifting, whereas with 5-axis FDM in its infancy I'm currently bootstrap all myself: planar/non-planar slicing (works) and now I reach the decision whether to implement inverse kinemantic (tooltip coord vs machine coord) in the firmware or leave to the post-processor (prototype exists already).

      I might end up doing both approaches and then see which one is more usable in real life.

      posted in CNC
      xyzdims
      xyzdims
    • RE: 5-axis CNC/FDM: Tool vs Machine Coordinates (Inverse Kinematics)

      @joergs5 I'm aware of head/head vs head/bed vs bed/bed 3 + 2-axis combination, I even mentioned in the article on PAX printhead: https://xyzdims.com/2021/02/08/3d-printing-penta-axis-pax-5-axis-printing-option/#Taking_Advantage_of_5_Axis and also in the References of the same article I list Epit3D Delta with rotating & tilting bed doing that - I might later explore the head/bed or bed/bed option too for tilt/rotation, but I first want to study the head/head tilt/rotation option thoroughly to really study the edge cases and feasibility overall.

      posted in CNC
      xyzdims
      xyzdims

    Latest posts made by xyzdims

    • RE: RepRapFirmware Python API

      @stuartofmt I have two use cases: https://github.com/Spiritdude/Prynt3r is a CLI tool (a rewrite of Print3r), there I have two modes:

      • to print .gcode, right now I upload and print, and then poll on print_status until the end of print, and remove the file again - works fine
      • to launch a CLI console, where I can send individual gcodes, this helps me to tune printers, there I found it hard to rely on /rr_reply response - anyway, before I go ahead to I need to reflect on @MintyTrebor feedback more

      Context: I do most of my development via CLI, and Prynt3r/Print3r is

      • slicer agnostic, it supports 10+ slicers
      • all my prints are logged, with all settings - I can go back 5+ years to see what settings a print had (I attach QR codes to prints identifying the print with the log file), etc
      • local (USB) and remote printers (TCP or RRF, hence the the library) are supported, as "TCP" I use ser2net and socat combination, which makes it all look serial from within Prynt3r

      and now I wanted "native" RRF support using HTTP layer.

      posted in Third-party software
      xyzdims
      xyzdims
    • RE: RepRapFirmware Python API

      @MintyTrebor thanks a lot for the explanation.

      I need to ponder on this a bit, to see in which direction I adapt the things you said. In a way, the RestAPI is lower level than a USB connection, which helps DWC but for me it likely means I need to reimplement a layer which RRF does for USB connection.

      I don't mind about G28 special case, but anything long running commands like heating are more critical, and I end up with various special cases.

      posted in Third-party software
      xyzdims
      xyzdims
    • RE: RepRapFirmware Python API

      @MintyTrebor so all commands like /rr_gcode which are shorter than 8s to execute, I get reliable response when I start with /rr_connect as session initiator; for homing or heating the nozzle via /rr_gcode I exceed the 8s, so the response via /rr_reply likely will be caught by DWC or whoever catches it first, correct?

      posted in Third-party software
      xyzdims
      xyzdims
    • RepRapFirmware Python API

      For a project I required a basic API to connect to RRF boards via HTTP, and wrote a small wrapper (I didn't find anything alike):

      https://github.com/Spiritdude/RepRapFirmwarePyAPI

      import RepRapFirmwareAPI
      
      rrf = RepRapFirmwareAPI.RRFRestAPI("192.168.0.12")
      
      resp = rrf.gcode("M122")              # synchronous
      # Note: type(resp) is either dict containing data, or str 
      
      rrf.gcode("M122","async")             # asynchronous
      resp = rrf.reply()
      
      rrf.upload("test.gcode","/gcodes/test.gcode")
      rrf.print("/gcodes/test.gcode")
      rrf.print_status()
      

      gcode() by default operates synchronous, it waits for a sensible reply, but the behavior can be changed; also, reply() by default is asynchronous, but can be changed as well.

      Note: the API is very experimental, if you adapt/adopt it, please check back the github the next weeks (e.g. until March 2023) to follow changes, e.g. in behavior.

      Any feedback to improve Python API is appreciated.

      @T3P3Tony or @dc42 as of current RRF there is no support for HTTP sessions - so when I send some command via HTTP and poll with /rr_reply, for a long running gcode() command (like heating or homing), the web-GUI running in a browser likely interferes?

      posted in Third-party software
      xyzdims
      xyzdims
    • RE: Kinematics: Interpolation with Non-Linear Motor Moves

      @dc42 I think I have a conceptual solution for my dilemma:

      RRF right now (as of 3.4) as far I understood:

      • machine position [mm]
      • motor position [microsteps]

      but I need to think, as for 5-axis PAX kinematics, in 3 layers:

      1. machine position [mm] - that is what G-code states
      2. motor position [mm] - that what motors have to do in [mm] after inverse kinematics calculated
      3. motor steps [microsteps] - that's the actual steps to perform

      Right now I do 2 & 3 in CartesianToMotorSteps(), which isn't ideal, and the trick with LimitPosition() as @JoergS5 mentioned to get start & end machine position helps a bit.

      I think in the long term RRF might introduce the layer 2), and make a hard distinction of layer 1, 2 and 3.

      The segmentation count and length in my opinion must be calculated from 2), and consider 3) as lower-bound. If segmentation is only calculated at 1) level, then the other involved motor moves as part of inverse kinematics cannot be determined or CartesianToMotorSteps() needs to be called (where inverse kinematics is calculated) but with motor steps given back that's not sufficient details or is it?

      In a nutshell, machine movements do not reflect actual motor movements, no assumption can be made outside of Kinematics which motor actually have to move and how much (!!) - therefore such layer 2) might be worth to introduce in the long term, e.g. Kinematics::CalculateMotorPosition().

      From top-down:

      • Gcodes::DoStraightMove(): Gcode positions in [mm]
        • Kinematics::LimitPosition()
        • Kinematics::CalculateMotorPosition() [new] transforming "tool or machine position" to "motor position" yet still in [mm], e.g. applying inverse kinematics, if not implemented, machine position [mm] => motor position [mm]
        • Kinematics::MotorPositionToMotorSteps() (formerly known as CartesianToMotorSteps()), calculating [mm] into [steps], if not implemented simply do motorPos[axis] * stepsPerMm[axis]
        • DDA::*(): deals with actual steps / time

      or in short: MachinePosition[mm] -> MotorPosition[mm] -> MotorSteps[steps]

      Anyway, these are my thoughts based on my limited comprehension of RRF.

      posted in Firmware developers
      xyzdims
      xyzdims
    • RE: Kinematics: Interpolation with Non-Linear Motor Moves

      Adding to my own reply, the hidden issue I struggle with is, machine pos are real absolute positions [e.g. mm] of the tool tip, where as motor pos are arbitrary units again (microsteps etc), yet, segmenting is optimizing ridiculous short movements [in mm], yet, motor pos gives me mechanical lower bound, but not information about absolute movement as of [mm] - so, what does segmenting really do?

      Does it define shortest possible movement motor-wise, or defined highest resolution of the motion of the tool tip?

      posted in Firmware developers
      xyzdims
      xyzdims
    • RE: Kinematics: Interpolation with Non-Linear Motor Moves

      @dc42 I thought about it the past days, I definitely like to hear @JoergS5 thoughts as well, as his kinematics has more rotary axes than mine.

      So, for my case there are just two rotary axes, Z rotation (A) and tilt (B), which means, that depending on angle of B, the segmentation of A is determined:

      • 0° B angle => A rotation segments = 1
      • 90° B angle => A rotation segments = max
      • 180° B angle => A rotation segments = 1

      which leads me to something like
      => A rotation segmentation = ( sin(B angle) * A angle-delta * A seg-factor ) + 1

      (This formula disregards when B is also rotating together with A; I likely have to calculate it twice or more with B angle-start and B angle-end)

      Segmenting for B rotation is not depending on other axes, therefore:
      => B rotation segmentation = B angle-delta * B seg-factor

      In order to determine the segmentation of a single rotary axis properly, I need to know the other positions and angles (start & end) too, so those need to be passed on or made accessible in the method/function.

      In a way, the actual movement length to calculate amount of segments I cannot determine from machine position delta, but from motor position delta, that is consultingCartesianToMotorSteps(). As I understand the current state of code of Gcodes.cpp: GCodes::DoStraightMove() these are machine positions.

      So conceptually, any actual segmentation must be calculated from actual motor position delta, because at the end of the day, the motors move, and the segmentation is a way to optimize those. The machine position is conceptually higher and more abstract and there I can't really know the kinematics and how much the individual motors really move - in linear context machine pos ~ motor pos, but in non-linear context with more complex inverse kinematics, this is not the case anymore.

      @dc42 It took me a bit longer to get back to this, I'm also not sure of the best way to proceed.

      To sum up [my current state of conclusion]: the segmentation of (motor-) movements needs motor positions (not machine positions), and the Kinematics class can provide that, and optimize certain cases (as I pointed out near the beginning of this reply).

      posted in Firmware developers
      xyzdims
      xyzdims
    • RE: Kinematics: Interpolation with Non-Linear Motor Moves

      @dc42 yes, saw it; I didn't want to impose my haste solution (better would be loop over numVisibleAxes to calculate moveLength), and your response kind of indicated that you ponder on a proper solution. I keep my haste patch for now until you come up with something proper.

      Update: there might be a requirement to transform degrees to moveLength somehow (a multiplier would be sufficient - maybe not on a second thought) provided from underlying kinematics using rotary axes; but I'm not fluent enough in C++ to do this properly.

      posted in Firmware developers
      xyzdims
      xyzdims
    • RE: Kinematics: Interpolation with Non-Linear Motor Moves

      @joergs5 Interesting observation of yours - it really helps me to understand RRF better, thanks for that. I need to ponder on this all some more 🙂 as I can't see what the proper solution is.

      posted in Firmware developers
      xyzdims
      xyzdims
    • RE: Kinematics: Interpolation with Non-Linear Motor Moves

      @joergs5 It seemed to me as if it's motor position, because my kinematics is only called once with end position. The GetLinearAxes() does return AxesBitmap::MakeFromRaw(0) treating none of the axes as linear (or am I misunderstanding of it?) Otherwise I inherent the ZLeadscrewKinematics, where segmentsPerSecond=100, minSegmentLength=0.2000.

      @dc42 I did this:

      PAXKinematics::PAXKinematics() noexcept
         : ZLeadscrewKinematics(KinematicsType::pax, SegmentationType(true, true, true))   // useSeg, useZSeg, useG0Seg
      {
      }
      

      and as mentioned above, GetSegmentsPerSecond() returns 100, and GetMinSegmentLength() returns 0.2.

      What am I missing otherwise? The PAXKinematics::CartesianToMotorSteps() is called only once with G1 B20 (to keep the example simple).

      I went ahead to understand it better what the problem could be, and dived into Gcodes/Gcodes.cpp function GCodes::DoStraightMove(): where the amount of segments are calculated, line #2110:

      		// Apply segmentation if necessary. To speed up simulation on SCARA printers, we don't apply kinematics segmentation when simulating.
      		// As soon as we set segmentsLeft nonzero, the Move process will assume that the move is ready to take, so this must be the last thing we do.
      		if (st.useSegmentation && simulationMode != 1 && (moveBuffer.hasPositiveExtrusion || moveBuffer.isCoordinated || st.useG0Segmentation))
      		{
      			debugPrintf("calculate segmentation\n");
      			// This kinematics approximates linear motion by means of segmentation
      			float moveLengthSquared = fsquare(currentUserPosition[X_AXIS] - initialUserPosition[X_AXIS]) + fsquare(currentUserPosition[Y_AXIS] - initialUserPosition[Y_AXIS]);
      			if (st.useZSegmentation)
      			{
      				moveLengthSquared += fsquare(currentUserPosition[Z_AXIS] - initialUserPosition[Z_AXIS]);
      			}
      			const float moveLength = fastSqrtf(moveLengthSquared);
      			const float moveTime = moveLength/moveBuffer.feedRate;			// this is a best-case time, often the move will take longer
      			moveBuffer.totalSegments = (unsigned int)max<long>(1, lrintf(min<float>(moveLength * kin.GetReciprocalMinSegmentLength(), moveTime * kin.GetSegmentsPerSecond())));
      		}
      

      it enters that if block, but the result is

      calculate segmentation
      moveLength = 0.0000, moveTime = 0.0000, kin.GetReciprocalMinSegmentLength = 5.0000, kin.GetSegmentsPerSecond = 100.0000
      mb.totalSegments = 1, mb.isCoordinated = 1, useSegmentation = 1, simulationMode = 0
      

      mb = moveBuffer

      Now, I did override moveBuffer.totalSegments = 10 (after the quoted if block) and now the motor moves exactly as I expected.

      At my superficial glance of that particular if block, the axes A & B are disregarded there, so, I added in haste following lines (indicated by ** in front):

      			if (st.useZSegmentation)
      			{
      				moveLengthSquared += fsquare(currentUserPosition[Z_AXIS] - initialUserPosition[Z_AXIS]);
      			}
      			**moveLengthSquared += fsquare(currentUserPosition[3] - initialUserPosition[3]);
      			**moveLengthSquared += fsquare(currentUserPosition[4] - initialUserPosition[4]);
      

      A[axis=3] and B[axis=4] are in degrees, so it's not really "length", but anyway, it produces this debug output:

      calculate segmentation
      moveLength = 45.0000, moveTime = 0.1350, kin.GetReciprocalMinSegmentLength = 5.0000, kin.GetSegmentsPerSecond = 100.0000
      mb.totalSegments = 13, mb.isCoordinated = 1, useSegmentation = 1, simulationMode = 0
      

      and now I have much better mb.totalSegments = 13.

      @dc42 am I missing some settings in my kinematics or is the segmentation computation problem actually higher up at Gcodes/Gcode.cpp?

      With the two axes (A & B) hardcoded into segments calculation the firmware & machine behaves now as I desired https://www.youtube.com/watch?v=TiV0zEc5spw - the nozzle stays in place, A & B are rotating, whereas X, Y & Z compensate properly.

      posted in Firmware developers
      xyzdims
      xyzdims