FullControlXYZ / fullcontrol

Python version of FullControl for toolpath design (and more) - the readme below is best source of information
GNU General Public License v3.0
634 stars 72 forks source link

support for grbl laser cutters #98

Open dvc94ch opened 1 month ago

dvc94ch commented 1 month ago

would at least require supplying a custom tool to the state to override gcode_e.

fullcontrol-xyz commented 1 month ago

Hey, good plan. Aside from getting rid of the E term at the end of G1 commands, is there anything else required?

dvc94ch commented 1 month ago

Don't know yet, still working with the plotter to get the geometry right. To vary the Energy/Area to produce a gradient I guess the speed can be varied instead of laser power. In addition turning the laser on/off before/after a G1 block might be necessary using M3/M5. Don't recall if G1 moves do that automatically.

fullcontrol-xyz commented 1 month ago

These are all easy enough to do 👍 if you write your exact GCode requirements and any additional design requirements (e.g. to change laser power) and associated GCode for them, I'll implement a custom laser cutting FullControl

dvc94ch commented 1 month ago
  1. Set laser power with M3 Sx where x is between 0-1000 (M4 can also be used which will scale S while accelerating/decelerating to prevent over burn)
  2. Disable laser with M5 at the end
  3. Make sure no Z or E is emitted
  4. Have a spot size config for the plotter to extrude the geometry on G1/G2/G3 moves
  5. Make sure G91 and G21 are specified (relative/mm)

Doesn't seem like producing a gradient is possible as you can't specify an acceleration or an increment like with E in marlin.

Is this sufficient information?

Computing the Energy per Area would be a bonus:

E/A = max_power (S/1000) / F spot_size

Where max_power is in W, F in mm/s, spot_size in mm seems to work out (units are J/mm2).

fullcontrol-xyz commented 1 month ago

Mostly looks easy but is G91 required. Can it work with G90. FullControl already works with G90 but would need revising to make it work with G91. For G21, that's fine and can appear at the start of the program.

Also for point 4, do you mean so that the fullcontrol preview fc.transform(steps, 'plot') shows the correct line width?

dvc94ch commented 1 month ago

G90 is also fine. Well the existing extruder geometry works fine. But if you disable the extruder you only emit G0 moves. But a 2d circular geometry might be more accurate, if the beam profile can be ignored since the laser is strongest at the center and gets weaker as it spreads outwards.

fullcontrol-xyz commented 1 month ago

What do you mean by "But if you disable the extruder you only emit G0 moves" - are G0 moves not good for you?

And also, what do you mean by 2d circular geometry? Ignoring the beam profile, won't the path look almost exactly like the currently preview if it's viewed top-down, aside from minor issues at corners?

dvc94ch commented 1 month ago

What do you mean by "But if you disable the extruder you only emit G0 moves" - are G0 moves not good for you?

grbl disables the laser on G0 moves. right now I use fc.Extruder(on=True/False) to switch from G0/G1 and this gives the correct plots. but this emits E gcode.

And also, what do you mean by 2d circular geometry? Ignoring the beam profile, won't the path look almost exactly like the currently preview if it's viewed top-down, aside from minor issues at corners?

yes that's right

dvc94ch commented 1 month ago

Think this would be really cool. These features would also let people produce gcode for CNC, although you'd need to add a subtractive mode to the plotter if you wanted to do that.

Currently working on a 2d slicer (left-right fill and outline fill) using the opencascade kernel (via the build123d python package). Think extending it to 3d wouldn't be too hard, just intersect the model with a z-plane would be sufficient to support stereolithography, and adding support for partial fill layers and overhang supports would support FDM.

fullcontrol-xyz commented 1 month ago

Ah I understand what you meant with G0/G1. Yep I'll also make it so G1 has no E value. With regards to the plot, would it make most sense for the designer to specify laser width or laser power - e.g. designer directly writes fc.Laser(spotsize=XX) or with fc.Laser(power=XX)

It would be nice if the designer didn't need to write the same information twice (once to control the line width in the plot and once to control the gcode M3 commands. Is the relationship between spotsize and power always known (even for basic users)? If so, we can have the user design with either spotsize or power, as they prefer, and fullcontrol will update the other parameter as necessary.

With regards to filling strategies, I need to get this stuff all together. I'm in touch with a few different people, who have all developed different strategies, and need to get everyone working cohesively. I intend to create some 'Projects' within this github library in the next week or two. One will be for filling. I'll try to remember to tag you or this issue when the project is live. But keep an eye out. There are so many things that can be reused across machining, robotics, 3D printing, laser cutting, etc.

FYI I'm already doing extrusion and machining at my university as research. So am also keen for a nice viewer to simulate the additive+subtractive toolpath. Ideally, build directly into FullControl python rather than needing to export to a CAD/CAM package (although that would be useful to support too! But their additive previews are nowhere near the standard/value of their subtractive ones). Anyway, it's in the pipeline!

Let me know about fc.Laser(spotsize=XX) or fc.Laser(power=XX) or something else

dvc94ch commented 1 month ago

you need to specify the spot size and the power. the spot size is the diameter at which the beam intensity drops below 1/e2, while the power determines the pulse width. so at 100% power the laser is always on, at 1% power the laser is only on 1% of the time.

dvc94ch commented 1 month ago

already made progress on the slicing:

newplot

here is a resistor and capacitor sliced using outline fill, for patterning a layer of graphene on polyimide film. there are still a few issues with it as you can see, but it's getting there

fullcontrol-xyz commented 1 month ago

Ah, very cool! Note that there is an offline path function in fclab.offset_path().

Can spot size be changed during the process, or is it something that is typically set once and left? Not just for your cutter, but for laser cutters in general? This will affect whether it is best as a design object or an initialization parameter.

Laser cutting is quite nice compared to extrusion 3D printing cos it doesn't matter if you go over the same area twice or overlap your lines too much, am I right? Since if the laser goes over hole, it doesn't matter? With extrusion 3D printing, you'd end up with too much material being deposited, so you need to be careful to get just the right overlap (too little = bad porous structure; too much = messy over-deposition).

dvc94ch commented 1 month ago

Spot size is a property of the laser. You could do a tool change during the job, but I think it's fine as an initialization parameter.

Yes, gave the builtin offset a go, but since I'm already using build123d to define the geometry to export for cad etc. seems like doing something custom is fine. Also easier to handle curves, the builtin only handles polygons.

dvc94ch commented 1 month ago

I think laser cutting is worse, with 3d printing, you can speed up, but with a laser cutter the spot size is given. If you're just cutting I guess it doesn't matter, but for my use case I want to get the right amount of energy on the plastic film as uniformly as possible. First to graphenize the sheet (if you over or under expose, the graphene layer will have different conductivity) and later to dope the graphene lattice with B or N atoms to produce semiconductors by submerging the film in ammonia or boric acid. I don't know yet how finicky this will be yet as I haven't tried it yet.

On the other hand I'm designing the geometry to be multiples of spot size, with 3d printing you get what you get...

fullcontrol-xyz commented 1 month ago

Hey, chat gpt told me that some laser system do have adjustable spot size with dynamic focus and maybe also with Z adjustment. It doesn't really matter, but I'll make it so spot size could theoretically be adjusted during the process since we have that functionality so readily accessible in FullControl for variable extrusion width. Even if the most common method in FullControl is simply to state it as an initialization parameter.

For your energy requirements, I can definitely see the challenges, potentially more that for 3D printing as you say. And these mean opportunity for FullControl when you get to do fancy custom things to really get what you want rather than using pre-defined path strategies from automatic commercial software. I examined a PhD thesis that looked at this sort of thing for surface modification (talking all about things like energy density and pulse overlaps, etc. Very interesting stuff!

I think that's all the info I'll need, so I'll try to create a modified FullControl or script in the next few days.

dvc94ch commented 1 month ago

I guess it's possible, but I don't think it's a feature commonly supported by cheap laser cutters. Can you link the PhD thesis you're referring to? Sounds very interesting.

fullcontrol-xyz commented 1 month ago

I'm afraid I can't link to the thesis as it isn't public yet, as far as I'm aware. It wasn't investigating a similar topic to you, but the idea of uniform energy density is clearly of value to a wide range of applications/research within the overall laser processing field

dvc94ch commented 1 month ago

can I give it a try soon? some methods on the laser to configure the energy density would be nice. this is what I'm currently using.

class Laser:
    def __init__(self, max_speed=10000, max_power=5, spot_size=0.06):
        self.max_speed = max_speed
        self.max_power = max_power
        self.spot_size = spot_size

    def energy_density(self, s=1000, f=None):
        power = self.max_power * s / 1000
        speed = (f or self.max_speed) / 60
        return power / (speed * self.spot_size)

    def laser_s(self, energy_density, f=None):
        speed = (f or self.max_speed) / 60
        power = energy_density * speed * self.spot_size
        return round(power / self.max_power * 1000)

    def laser_f(self, energy_density, s=1000):
        power = self.max_power * s / 1000
        speed = power / energy_density / self.spot_size
        return round(speed * 60)

    def laser_sf(self, energy_density):
        cutoff = self.energy_density()
        if energy_density <= cutoff:
            s = self.laser_s(energy_density)
            f = self.max_speed
        else:
            s = 1000
            f = self.laser_f(energy_density)
        return (s, f)
fullcontrol-xyz commented 1 month ago

Done an initial implementation!

Check it out with this:

import fullcontrol as fc
import lab.fullcontrol as fclab
steps = fc.rectangleXY(fc.Point(x=10, y=10, z=0), 10, 10)
steps.append(fclab.Laser(cutting_speed=400, power=2.5, on=True))
steps.extend(fc.travel_to(fc.Point(x=12, y=12)))
steps.extend(fc.rectangleXY(fc.Point(x=12, y=12), 6, 6))
steps.insert(0, fclab.Laser(cutting_speed=800, travel_speed=3000, power=5, spotsize=0.8, on=True ))
steps.append(fc.ManualGcode(text='M5'))
# fc.transform(steps, 'plot', fc.PlotControls(color_type='print_sequence'))
print(fclab.transform(steps, 'laser_cutter_gcode', fc.GcodeControls(printer_name='generic'), show_tips=False))
dvc94ch commented 1 month ago

one additional thing would be adding a fclab.Laser(dynamic=True) to emit M4 instead of M3. will give it a try soon

fullcontrol-xyz commented 1 month ago

Just tweaked it to have options for either 'constant_power' or 'dynamic_power':

import fullcontrol as fc
import lab.fullcontrol as fclab
steps = fc.rectangleXY(fc.Point(x=10, y=10, z=0), 10, 10)
steps.append(fclab.Laser(cutting_speed=400, dynamic_power=2.5, on=True))
steps.extend(fc.travel_to(fc.Point(x=12, y=12)))
steps.extend(fc.rectangleXY(fc.Point(x=12, y=12), 6, 6))
steps.insert(0, fclab.Laser(cutting_speed=800, travel_speed=3000, constant_power=5, spotsize=0.8, on=True))
steps.append(fc.ManualGcode(text='M5'))
# fc.transform(steps, 'plot', fc.PlotControls(color_type='print_sequence'))
print(fclab.transform(steps, 'laser_cutter_gcode', fc.GcodeControls(printer_name='generic'), show_tips=False))

produces gcode:

G0 F8000 X10 Y10 
M3 S5.0
G1 F800 X20 
G1 Y20 
G1 X10 
G1 Y10 
M4 S2.5
G0 F3000 X12 Y12
G1 F400 X18 
G1 Y18 
G1 X12 
G1 Y12 
M5
dvc94ch commented 3 weeks ago

something is wrong with travel moves:

steps = [
    Laser(on=True, constant_power=None, dynamic_power=1000.0, cutting_speed=2500.0, travel_speed=10000.0, spotsize=0.06),
    Point(x=0.0, y=0.0, z=0.0, color=None),
]
fclab.transform(
    steps,
     'laser_cutter_gcode',
     fc.GcodeControls(
            printer_name='generic',
            initialization_data={
                'travel_speed': 10000.0,
            }),
        show_tips=False,
    )
)
G0 F10000 X0 Y0 None
M4 S1000.0
dvc94ch commented 3 weeks ago

FYI, bug was introduced in latest commit

fullcontrol-xyz commented 3 weeks ago

Fixed it now. Thanks for pointing it out!

dvc94ch commented 2 weeks ago

Still haven't found the ideal solution for generating toolpaths. After doing some custom generation for special cases, it felt non scalable, especially since the geometry would have to be exported from existing cad solutions.

After learning more about laser optics it seems that defocusing the laser does change the beam diameter and there is a simple relationship between the z axis and the beam width, as you suggested earlier.

Now I'm trying to find a solution to slice a shape using variable width sections. It seems that your paper convex does just that, but it seems focused on printer settings to achieve the filament width. Is there any description of how you compute these variable width offsets for an arbitrary polygon which might contain holes?

ES-Alexander commented 2 weeks ago

Now I'm trying to find a solution to slice a shape using variable width sections. It seems that your paper convex does just that, but it seems focused on printer settings to achieve the filament width. Is there any description of how you compute these variable width offsets for an arbitrary polygon which might contain holes?

In case it's relevant / of interest, I put a few ideas down a while ago around variable-width path generation, as part of a broader "shape slicing" type approach. I'm yet to finish implementation there, but there's at least some code as a starting point.

My main ideas there for hole handling are to perform the initial interpolative path generation assuming there are no holes, and then either jump over the holes as travel moves (i.e. "cut" the path), or warp the existing path lines out of the way around the holes (i.e. "push aside" or "bend" the path).

fullcontrol-xyz commented 2 weeks ago

Now I'm trying to find a solution to slice a shape using variable width sections. It seems that your paper convex does just that, but it seems focused on printer settings to achieve the filament width. Is there any description of how you compute these variable width offsets for an arbitrary polygon which might contain holes?

There isn't I'm afraid. With convex, you subdivide the shape into sections that can be 'streamline sliced'. I've used it for shapes with holes in before. For a square with a circular hole in, you can do it in two sections. One for the top half, one for the bottom. Then you run convex twice. For the top half, your 'top' outline would be the straight top line of the square, split into lots of points (probably not equidistant). The 'bottom' outline would be a short straight line, then a semicircle, then another short straight line. You'd need the same number of points on the 'top' and 'bottom' lines. I'd be tempted to do the top line as lots of coincident points at either end, or some other skew, where the points or biased towards the end of the line. This will give a nice segmentation transition to matched the natural linear bias resulting from a (typically equidistant) points on a circle. When E-Alexander says "bend the path" that's what I'm suggesting here (manually identifying the bend).

Is this for research? Happy to put more effort in and have a call if this is for publication.

Once you have the convex function working, you can just replace extrusion width objects generated with convex with laser power objects based on extrusion width.

dvc94ch commented 2 weeks ago

Is this for research? Happy to put more effort in and have a call if this is for publication.

I'm no researcher, I don't even own a bachelors degree... I do make a living as a software engineer.

I did find this paper and github repo, I'll see if I can integrate it with my workflow [0], which uses kicad to draw a schematic, a model + optimization algrithm to design the footprints from the schematic and then generating a toolpath from the pcb layout. my current issue is gaps in the toolpaths, due to fixed width offsetting, which is obviously no good.

dvc94ch commented 2 weeks ago

laser_sim

I think my laser simulation is working now taking (s,f,z) and laser/machine parameters into account. Here you can see the energy density and the cross sections in movement and perpendicular direction.

The main take away is to overlap lines by 50% which doubles the energy density in the overlapped areas and leads to a nice uniform density which drops to zero within the non overlapped area. depending on the pwm frequency, duty cycle and travel speed the dimensions can shrink in the travel direction.