-
Notifications
You must be signed in to change notification settings - Fork 28
Refactor for clarity and performance improvements, add CLI #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
f5c20f0 to
f78bac0
Compare
RichardBronosky
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found this PR to be too large to adequately review. I cloned the source repo locally and considered each commit individually. I read b6dd503 but it was a bit much for me to confidently approve.
I think we need to add [at a minimum] some gcode files to serve as before and after examples to be used to test code refactors and optimizations. But eventually you are going to need full blown unit tests.
commit 5591b6da3368859f0f97bd0c1c1212544dff47f6
README: update to reflect CLI usage
commit f78bac007c3376161c5d66eaac93404620e70db7
refactor for clarity+performance, and add CLI
commit 7c2d91dc5aa4b33c4b8c7b469a14f1cfbb70b6f4
incorporate extra functionality, add caching
commit b6dd50373ebe2621b4628621da9fd483147d8df8
refactor spline functionality, improve performance
commit a505e51617a62ed26bd62096d9faa62625cef1c0
ignore common unwanted files
| @@ -0,0 +1,2 @@ | |||
| *.gcode | |||
| **__pycache__** | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you should also include
*.pyc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to add [at a minimum] some gcode files to serve as before and after examples to be used to test code refactors and optimizations. But eventually you are going to need full blown unit tests.
Agreed that would be useful. I made my own test gcode file to compare against while I was refactoring so I could know the results weren't just garbage. From my verification, the only change that actually affects the generated output is the rounding in the writeLine function, because:
- it's now f-string style, which includes trailing zeros (e.g.
str(round(1.23999, 3))->'1.24', whereasf'{1.23999:.3f}'->'1.240') - this may slightly increase file sizes, but is processed faster, and is closer to normal slicer outputs - it now supports user-specified precision, which can be used to reduce file sizes for less accurate printers/prints
I think you should also include
*.pyc
I believe they're always generated inside a __pycache__ directory?
| - The model can't be too large in the X dimension, otherwise you'll get self intersections | ||
| # Usage | ||
| - Place your part preferably in the middle of your print plate with known center X coordinates | ||
| - Place the sliced GCode in the same directory as the Python script |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't gotten far enough in the review to know if it is still required to Place the sliced GCode in the same directory as the Python script, but a mature CLI tool should not require this. I'm just noting it here before I forget.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the argument works fine with a path. I decided not to change that usage line from Stefan's description because I expect most users won't be programmers, in which case it might be easier to leave like that, and someone who's comfortable with CLIs would likely try to just specify a path if it's more convenient to do so.
That said, I don't feel strongly about it, so would be happy for it to be changed to better reflect the actual usage. Perhaps this could end up being pip-installable as a script or something, in which case that would be more important.
| for index, currentLine in enumerate(gcodeFile): | ||
| if index % 1000 == 0: # track progress | ||
| print(f"[{timedelta(seconds=perf_counter()-start)}]: line {index}\r", end='') | ||
| if currentLine[0] == ";": #if NOT a comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this say if IS a comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See, I think it should, but it's Stefan's comment, and I'm not certain if he intended it to be commenting about the block being entered, or what happens if execution gets past that block... It's his project, so he gets to choose the commenting style, even if it means I'm stuck between severely disliking this style or it just actually being a mistake.
| outputFile.write(currentLine) | ||
| continue | ||
| if currentLine.find("G90 ") != -1: #set absolute mode | ||
| if currentLine.startswith("G90 "): #set absolute mode |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You removed the trailing space on L72 but left it in L76. Is there a reason for this or was it an oversight?
I feel that both str().startswith and str().find are brittle and should be replaced with a dedicated function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You removed the trailing space on L72 but left it in L76. Is there a reason for this or was it an oversight?
I think I actually added an extra space on 72 so the comments would line up between 72 and 76, since they already lined up (which can aid readability). Once again, not sure if an intentional commenting style from Stefan, or just the number of spaces that happened to end up on those lines.
I feel that both
str().startswithandstr().findare brittle and should be replaced with a dedicated function.
find could conceivably be incorrect if G90 appears in a comment. startswith should be correct, although could be marginally more robust with an lstrip() first. I chose to avoid that because it's then less clear, and I figured the input gcode is expected to come from a slicer, and I don't believe they ever put spaces before lines, but perhaps that's not the case (maybe some use indenting or something?).
Do you have any particular preference as to what should be there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More generally, I expect a bunch of the logic throughout this function could be cleaned up somewhat, I just left it alone beyond where I thought something was error-prone, or needed to change due to the spline refactoring. In my profiling the logic itself wasn't an obvious performance throttle, so I figured my time would be more efficiently spent elsewhere :-)
| from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter | ||
|
|
||
| parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) | ||
| parser.add_argument("in_file", help="input file name (*.gcode)") | ||
| parser.add_argument("-o", "--out_file", help="output filename, default 'BENT_{in_file}'") | ||
| parser.add_argument("-x", "--x_values", default=(125, 95), nargs="*", type=float, | ||
| help=("x values that define the spline, space-separated (e.g. '125 50 33')." | ||
| " First should be in the center of your part.")) | ||
| parser.add_argument("-z", "--z_values", default=(0, 140), nargs="*", type=float, | ||
| help=("corresponding z values that define the spline, space-separated." | ||
| " First should be 0 (e.g. '0 80 140').")) | ||
| parser.add_argument("-l", "--layer_height", default=0.3, type=float, | ||
| help="layer height of the sliced gcode [mm].") | ||
| parser.add_argument("-a", "--warning_angle", default=30, type=float, | ||
| help="Maximum angle [degrees] printable with your setup") | ||
| parser.add_argument("-b", "--bend_angle", default=-30, type=float, | ||
| help="Angle [degrees] of the spline at the top point") | ||
| parser.add_argument("-d", "--discretization_length", default=0.01, type=float, | ||
| help="Discretization length for the spline length lookup table") | ||
| parser.add_argument("-s", "--skip_plot", action="store_true", | ||
| help="flag to skip plotting of the spline") | ||
| parser.add_argument("--xy_precision", type=int, default=4, | ||
| help="Decimals of precision to round x/y values to.") | ||
| parser.add_argument("--z_precision", type=int, default=3, | ||
| help="Decimals of precision to round z (height) values to.") | ||
| parser.add_argument("--e_precision", type=int, default=5, | ||
| help="Decimals of precision to round extrusion amounts to.") | ||
| parser.add_argument("--printer_dims", nargs=2, default=(200, 200), type=float, | ||
| help="printer width and height [mm], space-separated.") | ||
|
|
||
| args = parser.parse_args() | ||
|
|
||
| spline = Spline(args.x_values, args.z_values, args.discretization_length, | ||
| args.bend_angle) | ||
| if not args.skip_plot: | ||
| print("Press Q to close the plot and continue") | ||
| spline.plot(printer_dims=args.printer_dims) | ||
|
|
||
| out_file = args.out_file or f"BENT_{args.in_file}" | ||
|
|
||
| main(args.in_file, out_file, spline, args.layer_height, args.warning_angle, | ||
| args.xy_precision, args.z_precision, args.e_precision) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would have put all of this in a cli function, but that's just a style preference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see a particular benefit to that, given it's already in the script portion (so can't be imported), and is only 'called' once. I've never seen a cli function before, so am curious how it would be implemented. Are you thinking something like
def cli():
parser = ArgumentParser()
parser.add_argument(...)
...
return parser.parse_args()or would the parser be returned instead of the parsed arguments?
Given it's your preferred approach, are there benefits I'm missing?
thereeroyz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dont now frame
Hi @CNCKitchen, cool project!
I had a look at the code and thought it could be fun to help out by doing some refactoring.
Here's a brief summary of my changes/improvements:
python3 bend_gcode.py my_file.gcode, orpython3 bend_gcode.py --helpto see all the options)