supermerill / SuperSlicer

G-code generator for 3D printers (Prusa, Voron, Creality, etc.)
4.05k stars 516 forks source link

Form1+ Slicing OpenFL #227

Open lavachemist opened 4 years ago

lavachemist commented 4 years ago

This a new feature request.

Supermerill, Chirs Warkocki in the Prusa Community Form suggested that I reach out to you.

I have been testing and writing OpenFL scripts for the Formlabs Form1+ in Python and I'd like to move away from the dated OpenFL version of Preform. OpenFL has enough information for us to be able to build FLP files, but I'd like to make a plugin for Slic3r or PrusaSlicer that builds the FLP files and writes them to the Form1/1+ over USB. Is this something that is realistically possible, and if so, where should I start?

Version

Version of Slic3r++ used goes here

Use About->About Slic3r++ for release versions

For -dev versions, use git describe --tag or get the hash value for the version you downloaded or git rev-parse HEAD

Operating system type + version

What OS are you using, and state any version #s In case of 3D rendering issues, please attach the content of menu Help -> System Info dialog

3D printer brand / version + firmware version (if known)

What 3D printer brand / version are you printing on, is it a stock model or did you modify the printer, what firmware is running on your printer, version of the firmware #s

Behavior

Is this a new feature request?

Project File (.3MF) where problem occurs

Upload a Slic3r++ Project File (.3MF) (Plater -> Export plate as 3MF for Slic3r PE 1.41.2 and older, File -> Save / Save Project for Slic3r++, Slic3r PE 1.42.0-alpha and newer)

supermerill commented 4 years ago

Everything is possible, with enough time and dedication.

form1 is a sla printer? what do you need? (images, gcode for filament, gui) what do you want from slic3r? (in term of objects, as parameter for your code)

if it's adding a setting to change the export format of the sla export of slic3r, imo, it's better if it's in c++ inside the software. Disclaimer: i don't have any sla printer, and i didn't look inside the sla code / workflow done by prusa.

lavachemist commented 4 years ago

Yes, Form 1 is a galvo laser style SLA printer.

The printer does not use g-code, it uses files that have plain text instructions, each layer is a separate file written to the printer over USB.

I think we have two options. We either need to find a way to make Slic3r directly write the files, or create a parser that can take g-code from Slic3r and translate it to instructions the printer can understand.

Formlabs created OpenFL, which is an API for the printer. So we already know how to control the printer, it's the slicer to printer part that is less clear to me.

Given all that, do you have any suggestions of where I should start? I'm willing to learn, but I don't really know where to begin.

On Thu, May 7, 2020, 7:45 AM Merill notifications@github.com wrote:

Everything is possible, with enough time and dedication.

form1 is a sla printer? what do you need? what do you want from slic3r ?

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/supermerill/Slic3r/issues/227#issuecomment-625204847, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALQDL56VCUKYPTJ7Q6TMUMDRQKNPFANCNFSM4M2ZPQ5A .

supermerill commented 4 years ago

Just to be sure, it's using a laser that have to draw lines, like for a fff printer? (it's not a dlc- based printer that only need layer svg)

lavachemist commented 4 years ago

That's correct, it's drawing lines like fff. No SVG files needed.

On Thu, May 7, 2020, 8:25 AM Merill notifications@github.com wrote:

Just to be sure, it's using a laser that have to draw lines, like for a fff printer? (it's not a dlc- based printer that only need layer svg)

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/supermerill/Slic3r/issues/227#issuecomment-625223288, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALQDL5Y3Y4MHPUY6G2A4523RQKSDTANCNFSM4M2ZPQ5A .

supermerill commented 4 years ago

Best way is the create an other gcode generator that generate command from the lines to print. printer- I was considering adding a script language to allow to create printer/firmware-specific commands. I think it's the right move also to allow you to write your own file.

I was reviewing the langages for the script. i was thinking about:

lavachemist commented 4 years ago

Okay, cool. I will work on creating a script that can translate g-code to the FLP commands that the printer can understand.

As far as adding scripting to Slic3r goes, for the Form1+, Python would be easiest not only for the reasons you mentioned (I already know Python!), but also because the OpenFL API is a Python library.

On Thu, May 7, 2020, 1:31 PM Merill notifications@github.com wrote:

Best way is the create an other gcode generator that generate command from the lines to print. printer- I was considering adding a script language to allow to create printer/firmware-specific commands. I think it's the right move also to allow you to write your own file.

I was reviewing the langages for the script. i was thinking about:

  • angelscript : because it's easy to add, efficient (as efficient a script can be), made to script c++.
  • python: mainly because many people use it, but it's much more difficult to integrate and should run slower.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/supermerill/Slic3r/issues/227#issuecomment-625394263, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALQDL56JHIQ5FQBDZRCJX23RQLV6PANCNFSM4M2ZPQ5A .

supermerill commented 4 years ago

I mean, with the scripting built-in, you won't even write gcode in the first place. there's a class called "gcode-writer" basically, it's called to write the file, for exemple, here is a method:

std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string &comment)
{
    m_pos.x() = point.x();
    m_pos.y() = point.y();
    m_extruder->extrude(dE);

    std::ostringstream gcode;
    gcode << "G1 X" << XYZF_NUM(point.x())
          <<   " Y" << XYZF_NUM(point.y())
          <<    " " << m_extrusion_axis << E_NUM(m_extruder->E());
    COMMENT(comment);
    gcode << "\n";
    return gcode.str();
}

If we can call a script for each method, you can output want you want

def extrude_to_xy(next_point, dE, comment):
    return "what you want in the file for this command"

Or you can also create your own FLPPrinter class for this fork if you're in a hurry. I can help you with settings and little things.

lavachemist commented 4 years ago

Oh! Even better. It actually seems like it might be fairly straightforward since it still takes similar commands, they just aren't g-code.

The other thing that comes to mind as a potential sticking point is that the Form uses incremental rather than absolute positioning (at least for Z axis, I'm not sure about X/Y or tilt). I'm guessing that worst case it would be as simple as creating a function that takes an absolute command and converts it to incremental.

Here's an example of some FLP Z move commands:

0x01 LaserPowerLevel 0 0x05 ZCurrent 80 0x04 ZFeedRate 5999 0x03 ZMove 78670 0x0a WaitForMovesToComplete

and an example of some XY commands:

0x01 LaserPowerLevel 39512 0x00 XYMove 3 LaserPoint(x=25369, y=31080, dt=52) LaserPoint(x=25404, y=31193, dt=10) LaserPoint(x=25396, y=31183, dt=1)

On Fri, May 8, 2020, 12:51 PM Merill notifications@github.com wrote:

I mean, with the scripting built-in, you won't even write gcode in the first place. there's a class called "gcode-writer" basically, it's called to write the file, for exemple, here is a method:

std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string &comment) { m_pos.x() = point.x(); m_pos.y() = point.y(); m_extruder->extrude(dE);

std::ostringstream gcode;
gcode << "G1 X" << XYZF_NUM(point.x())
      <<   " Y" << XYZF_NUM(point.y())
      <<    " " << m_extrusion_axis << E_NUM(m_extruder->E());
COMMENT(comment);
gcode << "\n";
return gcode.str();

}

If we can call a script for each method, you can output want you want

def extrude_to_xy(next_point, dE, comment): return "what you want to be printed"

Or you can also create your own FLPPrinter class for this fork if you're in a hurry. I can help you with settings and little things.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/supermerill/Slic3r/issues/227#issuecomment-625911521, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALQDL56JCVJIFBTNJSGHIKTRQQ2CTANCNFSM4M2ZPQ5A .

supermerill commented 4 years ago

you should be able to store a point as global variable and use it to convert the global pos to increment. (here it's using m_pos in the code snippet)

all functions to implement (some can be left empty):


    std::string preamble();
    std::string postamble() const;
    std::string set_temperature(unsigned int temperature, bool wait = false, int tool = -1) const;
    std::string set_bed_temperature(unsigned int temperature, bool wait = false);
    std::string set_fan(unsigned int speed, bool dont_save = false);
    std::string set_acceleration(unsigned int acceleration);
    std::string reset_e(bool force = false);
    std::string update_progress(unsigned int num, unsigned int tot, bool allow_100 = false) const;
    // return false if this extruder was already selected
    bool        need_toolchange(unsigned int extruder_id) const 
        { return m_extruder == nullptr || m_extruder->id() != extruder_id; }
    std::string set_extruder(unsigned int extruder_id)
        { return this->need_toolchange(extruder_id) ? this->toolchange(extruder_id) : ""; }
    // Prefix of the toolchange G-code line, to be used by the CoolingBuffer to separate sections of the G-code
    // printed with the same extruder.
    std::string toolchange_prefix() const;
    std::string toolchange(unsigned int extruder_id);
    std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string()) const;
    std::string travel_to_xy(const Vec2d &point, const std::string &comment = std::string());
    std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string());
    std::string travel_to_z(double z, const std::string &comment = std::string());
    bool        will_move_z(double z) const;
    std::string extrude_to_xy(const Vec2d &point, double dE, const std::string &comment = std::string());
    std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string());
    std::string retract(bool before_wipe = false);
    std::string retract_for_toolchange(bool before_wipe = false);
    std::string unretract();
    std::string lift();
    std::string unlift();

Note that you have to also write a gcodereader to be able to see the gcode preview, or it won't show up.

lavachemist commented 4 years ago

Nice. Is the scripting a long term goal, or is it something that might come sooner rather than later?

On Fri, May 8, 2020 at 5:02 PM Merill notifications@github.com wrote:

you should be able to store a point as global variable and use it to convert the global pos to increment.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/supermerill/Slic3r/issues/227#issuecomment-626019085, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALQDL5ZJSTFW3JN6RNKRH6TRQRXOTANCNFSM4M2ZPQ5A .

supermerill commented 4 years ago

It's a long term goal, like the bridge rework or variable-width perimeters. But if you have a real need, it's going to motivate me a bit. If you have a business and need it, there's an even more obvious way to force my motivation.

If you want to have something quickly made by yourself, you have to write a subclass of gcodewriter in c++. I can do the piping work quickly to help you start, if you prefer this way.

lavachemist commented 4 years ago

Understood. The ability to slice on a third party slicer is definitely a need for this project, but this is a community project, so unfortunately there isn't any funding - I wish that was the case!

I think for now, I will work on creating a g-code to FLP conversion script, and whenever the scripting portion happens I can switch to that. I don't know C++, so that's not really a viable option for getting something quickly.

On Tue, May 12, 2020 at 9:01 AM Merill notifications@github.com wrote:

It's a long term goal, like the bridge rework or variable-width perimeters. But if you have a real need, it's going to motivate me a bit. If you have a business and need it, there's a even more obvious way to force my motivation.

If you want to have something quickly made by yourself, you have to write a subclass of gcodewriter in c++. I can do the piping work quickly to help you start, if you prefer this way.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/supermerill/Slic3r/issues/227#issuecomment-627328080, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALQDL52FHP6CIJWORIHW7BTRRFCD7ANCNFSM4M2ZPQ5A .

supermerill commented 4 years ago

If you know how to program, it's not that hard to switch from language to language. most difficult task for c++ is to set up the building environment.

I'll post here if i have any news.

lavachemist commented 4 years ago

I've been editing SuperSlicer to add the OpenFL/Formlabs F1+ slicing, and I seem to be stuck. I thought that I would be able to add the additional g-code option by adding it to GCodeWriter.cpp, PrintConfig.cpp, and PrintConfig.hpp.

I can compile and build in Linux, but the additional g-code flavor does not appear in the menus. It is neither in the Printer Settings tab, nor in the Configuration Wizard. Are the menu items for the GUI defined somewhere else?

lavachemist commented 4 years ago

Here are the changes I've made. I'm basically just trying to get the additional flavor option at this point, I haven't put a lot of effort into actually configuring what the flavor does: SuperSlicerEdits_060420.zip

supermerill commented 4 years ago

It's better if you can just link the commit hash of your cloned repo. This way, i can really quickly and easily what's changed. i won't open a zip to make a manual diff. And you have to do it anyway to submit a pull request.

Advice: search for "reprap" or "klipper" in all files, ignoring the case, and add your new one where it's needed.

fight!

lavachemist commented 4 years ago

Thanks! I will figure out how to do that. I'm still learning how to properly use Github!

And thank you for the advice!

lavachemist commented 4 years ago

Here is the commit hash: commit hash: 4744ee72ad3c654f94998cc86f072cb2d5dd466e

lavachemist commented 4 years ago

I need to remove all semicolons ";" from the print files. I've gotten to the point that I only have a couple left and I am not sure how to get rid of them, can you help? image

commit hash is: 57e123e

supermerill commented 4 years ago

gcode.cpp line 1253

    // Write information on the generator.
    _write_format(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str());

you can do a big if that go to l1275

lavachemist commented 4 years ago

That worked. Thanks!

image

lavachemist commented 4 years ago

I need to reference the current speed within each X/Y galvo move command. Is there a way that I can call it during GCodeWriter::extrude_to_xy?

here is what I have for GCodeWriter::set_speed: image

here is what I have set for GCodeWriter::extrude_to_xy: image

I had hoped that XYZF_NUM(F) called from within the extrude function would have resulted in the speed value, but it does not. Here is some resulting FLP code:

image

I need the current XY feed rate for the "dt" output. Any ideas?

supermerill commented 4 years ago

in gcodewriter, you have m_last_acceleration you can create an other variable like m_last_speed You change the value of this variable in set_speed and you can use it everywhere.

lavachemist commented 4 years ago

I think I'm doing this wrong. here are speed settings: image

here are extrude settings: image

and here is the compile error: image

Update: changing the value to "m_last_speed = XYZF_NUM(F)" got rid of the first error, but the second error remains. It doesn't let me call the variable from within the extrude function.

supermerill commented 4 years ago

can't tell you what to change with only these screenshots, no obvious mistake.

lavachemist commented 4 years ago

Here's the latest commit: dcf6128

supermerill commented 4 years ago

you forgot to declare m_lastspeed in gcodewriter.hpp, like it's done for the other variables (that began by m).

lavachemist commented 4 years ago

EDIT: This actually seems to be working now. I'm not sure why, because it was definitely not working last night. I'm leaving the comment below, in case it is helpful for future reference, but I don't need help with this part anymore. I'm sure there will be more questions soon though!

Thanks! I've added the variable to the header, and that did change the behavior. However, it still does not do what I want and I now think I now know why.

GCodeWriter_Constant

The GCodeWriter::set_speed function is a constant. I can set the variable to be mutable, but I still don't get the speed changes that I expect, so I think I am barking up the wrong tree. I even tried removing the constant marker, but that just caused other issues. It's probably not surprising to someone who understands C++, but I'm learning on the fly.

So, do you think it makes more sense to just make a separate function for setting the speed of FLP commands? Or is there something else you can think of that I can try?

supermerill commented 4 years ago

To make it works, you have to remove the const from the .cpp but also from the definition in the .hpp (i think that's your problem, it's only called twice, and no obvious const needed)

I think I'm going to create a template for you to fill, that way you'll have a nice api to use and it will be cleaner, and more easy afterward to add more exotic output.

lavachemist commented 4 years ago

It turned out that it does work, I just made the variable mutable. I think that last night I was probably doing something wrong and had been looking at it too long.

I am going to have other problems to solve soon though. One is that I will need to change the power level of the laser, depending on whether it is printing an outline, infill or supports. I'm using extruder temperature right now, but that isn't a value that changes the way I need.

I think in the long run it will make a lot of sense to have an entire separate portion of the slicer that is for laserSLA, similar to how there is one for FDM and the SL1.

lavachemist commented 4 years ago

I need to access "layer_height" from within the GCodeWriter.cpp. Can you help me figure out how to do that?

I tried calling "PrintObjectConfig::layer_height" but that raises an error saying it is an "invalid use of non-static data member."

supermerill commented 4 years ago

It's not easy. Layer height is something that can vary from layer to layer and from object to object. You can't really took it like that.

If you want the current extrusion height, you have to pass it to your writer. Imo, the best entry point is GCode::_extrude(const ExtrusionPath &path, const std::string &description, double speed) as it's what every extrude method will called the end. you can call a new method in gcodewriter at the start of it, passing the path.height to define the new (if it changed) current extrusion height.

if you want the layer height as defined in the config, look at where the string "; printing object " is used in gcode.cpp, that's where you can take the m_config.layer_height (you may also want to initialize it at the start of gcode creation, with first_layer_height, this may need difficult investigation to do it right, you can look where it's used)

there is also some other place where you might want to call it, like in retraction, vase mode and 3D path but i don't think these are feature you want in sla, so don't bother.

for your error, it's because you need an object that store the data. PrintObjectConfig is the definition, and you need an instance to have the data. m_config in gcode.cpp is a variable that contain an instance of PrintObjectConfig (you can see it has this type in his definition in gcode.hpp)

lavachemist commented 4 years ago

Is this the part of gcode.cpp you're suggesting for getting the config layer height? image

I don't see layer_height near any instance of "printing object" in the gcode.cpp file when I search using Sublime. I would like to eventually be able to use variable layer height, but for now having access to the configured layer height would be good enough.

Maybe I'm going about this the wrong way though. My real issue is that the Form1+ uses relative positioning for the Z Axis, but the SuperSlicer uses absolute positioning. What I need to accomplish is to have the Z axis lift 5mm between each layer, and then lower the Z axis by 5mm minus the layer height. That accomplishes a peel and resets the Z height for the next layer in two moves. I can do this in the beginning of layer g-code but it's safer to have it done by the slicer.

supermerill commented 4 years ago

if it's what you want to do, you can reuse the lift and unlift of the gcode writer. if you ask for "retract on layer change", and zlift is set without the filters, a zlift will occur on every layer change and you can set an absurdly high value for min travel after retraction to disable other retractions. The chain should be something like lif() travel_to_z() unlift(). for the absolute vs relative, the writer store its position in m_pos, so you just have to do a subtraction.

for your question, no. layer_height is stored in a PrintObjectConfig . You have one in m_config in the GCode object. So in every GCode method, you can write 'm_config.layer_height' (a good IDE can tell you that with auto-completion). But it continas the good value only after being initialized, and this occur at line 2435. so you can pass it after that, not before.

lavachemist commented 4 years ago

Okay, I will take a look at all of that. For the m_pos, I'm thinking I can store the current m_pos.z() in a local variable called m_last_z at the end of the GCodeWriter::_travel_to_z function. Then I can subtract m_last_z from m_pos.z() at the beginning of the function, to get the layer height. Does that seem correct?

Also, I think I will have to wrap the travel_to_z function in an if statement so it does not apply to OpenFL, otherwise it will ignore the z move.

supermerill commented 4 years ago

you already have the old z at the start of travel_to_z , before m_pos.z = z.

For the rest, i don't follow you. Be sure to add a comment for almost every useful line added, to explain what you do and why.

lavachemist commented 4 years ago

Okay, here is what I have now. It works, but it does have some math issues that occasionally arise in line 483 when converting from a double to an int. I end up getting a number that is off by +/- 1. I am sure it's due to casting to an integer, but I can't figure out how to fix it. I tried using precision with XYZF_NUM, but that raises a type error.

image

supermerill commented 4 years ago

best way to do that is to always to the math on int where possible. set m_z_move to int m_z_move = int(m_pos.z() 400) - int(m_last_z400) [...] gcode << (m_z_move - 2000)

But if your layer height isn't a multiple of 1/400, you can't have your exact step. I'm working on another issue to ensure that a layer is always at a certain step, so stay tune on that.

lavachemist commented 4 years ago

I think I already tried the math that way but I will give it another shot.

I'm using layer heights of 0.1, 0.05 and 0.025mm. So it should always be able to resolve to a whole number (40, 20 or 10). The error doesn't seem to be any more or less frequent with any of those layer height values.

lavachemist commented 4 years ago

I spent a few hours last night troubleshooting the step loss issue with my friends in the Photonsters group. Then a really smart engineer joined the conversation and immediately identified the problem:

"IEEE floats are a binary number with a binary exponent

Which means, that anything to the right of the decimal is only accurately represented if it is expressible as a short divisor in powers of 2"

So, unless we can source an integer value (steps, microsteps, nm, etc.) somewhere else in the code we won't be able to get rid of that +/- 1 microstep. I'm guessing that since you said you're working on a similar issue, you are probably already aware of this, but I thought I'd share.

It's probably easy to ignore with FDM when your layer heights are 2-8x taller, but it becomes an issue when you're dealing with 0.05mm or 0.025mm layer heights.

lavachemist commented 4 years ago

Update - we just figured it out! The issue seems to have been related to the way that C++ handles casting from float/double to integer. When we used round() instead of int(), it fixed the problem.

supermerill commented 4 years ago

see here to how that can become complicated https://github.com/supermerill/SuperSlicer/commit/98c621773693dad03ed00ee2cb5e31e325f90568#diff-c4924319e99752de83e5f29ec0c0b991 and... i have forgot to round something.

lavachemist commented 4 years ago

Are you referring to the minimum and maximum layer height values?

If so, that was on my list of questions to ask you about. The limitation of layer height based on nozzle size is a problem for laser sla for a couple of reasons. First, the laser spot size is 150 micron for Form1+ and 70 micron for the Moai (we are testing the Moai laser in a Form1+). This creates the second problem, which is that we can realistically print anywhere from 0.025 up to 0.2mm layer heights. So, the limitations are going to be troublesome for sure.

I have disabled the limitation for now, so I can continue testing, but I meant to ask you about it later.

supermerill commented 4 years ago

when a new type of printer will be created, check should happen in a new separate function, so it will be tailor-made.

lavachemist commented 4 years ago

okay, cool. That is what I was thinking too. We also will need tree supports, which I think are not possible with FDM.

lavachemist commented 3 years ago

I'm almost to the point that I'm ready to add a new printer type for SLA. Can you offer any idea of how I would do that?

supermerill commented 3 years ago

Just a day or two and i'll do something for that.