Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fullcontrol/combinations/gcode_and_visualize/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# import functions and classes that will be accessible to the user
from .classes import *
from fullcontrol.common import fix
from fullcontrol.common import check, flatten, linspace, export_design, import_design, points_only, relative_point, first_point, last_point
from fullcontrol.common import check, flatten, linspace, export_design, import_design, points_only, relative_point, first_point, last_point, first_print_point, last_print_point, find_extruder_state
from fullcontrol.geometry import *
from fullcontrol.visualize.bounding_box import BoundingBox

Expand Down
2 changes: 1 addition & 1 deletion fullcontrol/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
from fullcontrol.auxilliary_components import Fan, Hotend, Buildplate
from fullcontrol.point import Point
from fullcontrol.printer import Printer
from fullcontrol.extra_functions import points_only, relative_point, flatten, linspace, first_point, last_point, export_design, import_design
from fullcontrol.extra_functions import points_only, relative_point, flatten, linspace, first_point, last_point, first_print_point, last_print_point, find_extruder_state, export_design, import_design
from fullcontrol.check import check, fix, check_points
79 changes: 79 additions & 0 deletions fullcontrol/extra_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,85 @@ def last_point(steps: list, fully_defined: bool = True) -> Point:
'''
return first_point(list(reversed(steps)), fully_defined)

def last_print_point(steps,extruder):
'''
Returns last point in steps where the extruder is on, the initial extruder state should be set.

Parameters:
- steps (list): a list of steps.
- extruder (bool): The initial state of the extruder.

Returns:
- Point: The last Point in the list with extruder on.

Raises:
- Exception if no point is found when extruder is on.

'''
last_point = None
for step in steps:
if type(step).__name__== "Extruder":
extruder = step.on
if type(step).__name__ == 'Point' and extruder:
last_point=step

if last_point is None:
raise Exception('No point found in steps with extruder on')

return last_point

def first_print_point(steps,extruder):
'''
Returns first point in steps where the extruder is on, the initial extruder state should be set.

Parameters:
- steps (list): a list of steps.
- extruder (bool): The initial state of the extruder.

Returns:
- Point: The first Point in the list with extruder on.

Raises:
- Exception: if no point is found when extruder is on.
'''
point_prospect = None # The first point needs to be the point immediatly before the extruder is turned on, so the latest point needs to be accesible.
first_point = None
for step in steps:
if type(step).__name__ == 'Point':
point_prospect=step
if type(step).__name__== "Extruder":
extruder = step.on
if extruder and point_prospect is not None:
first_point = point_prospect
break

if first_point is None:
raise Exception('No point found in steps with extruder on')

return first_point

def find_extruder_state(steps):
'''
Finds final extruder state if any changes occur in steps. Can be used to find extruder state for use with first_print_point and last_print_point.

Parameters:
- steps (list): a list of steps.

Returns:
- extruder state: The final extruder state (True/False)

Raises:
- Exception: if extruder state is not set in steps.
'''
extruder = None
for step in steps:
if type(step).__name__== "Extruder":
extruder = step.on

if extruder is None:
raise Exception('Extruder state not changed in steps.')

return extruder

def export_design(steps: list, filename: str):
'''
Expand Down
4 changes: 4 additions & 0 deletions fullcontrol/gcode/controls.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@ class GcodeControls(BaseModel):
initialization_data (Optional[dict]): Values passed for initialization_data overwrite the default initialization_data of the printer. Defaults to an empty dictionary.
save_as (Optional[str]): The file name to save the gcode as. Defaults to None resulting in no file being saved.
include_date (Optional[bool]): Whether to include the date in the filename. Defaults to True.
always_print_F (Optional[bool]): If True include speed in every gcode line irrespective of wether it changed. Defaults to False.
always_print_geometry (Optional[bool]): If True include x, y and z coordinate in every gcode line irrespective of wether each coordinate changed. Defaults to False.
"""
printer_name: Optional[str] = None
initialization_data: Optional[dict] = {} # values passed for initialization_data overwrite the default initialization_data of the printer
save_as: Optional[str] = None
include_date: Optional[bool] = True
always_print_F: Optional[bool] = False
always_print_geometry: Optional[bool] = False

def initialize(self):
if self.printer_name is None:
Expand Down
20 changes: 12 additions & 8 deletions fullcontrol/gcode/point.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,28 @@
class Point(BasePoint):
'Extend generic class with gcode methods to convert the object to gcode'

def XYZ_gcode(self, p) -> float:
def XYZ_gcode(self, p,always_print) -> float:
'''
Generate XYZ gcode string to move from a point p to this point.

Args:
p (Point): The point to move from.
always_print (bool): If True alway include all coordinates.

Returns:
str: The XYZ gcode string.

'''
s = ''
if self.x != None and self.x != p.x:
s += f'X{self.x:.6f}'.rstrip('0').rstrip('.') + ' '
if self.y != None and self.y != p.y:
s += f'Y{self.y:.6f}'.rstrip('0').rstrip('.') + ' '
if self.z != None and self.z != p.z:
s += f'Z{self.z:.6f}'.rstrip('0').rstrip('.') + ' '
if (self.x != None and self.x != p.x) or always_print == True:
x = self.x if self.x != None else p.x
s += f'X{x:.6f}'.rstrip('0').rstrip('.') + ' '
if (self.y != None and self.y != p.y) or always_print == True:
y = self.y if self.y != None else p.y
s += f'Y{y:.6f}'.rstrip('0').rstrip('.') + ' '
if (self.z != None and self.z != p.z) or always_print == True:
z = self.z if self.z != None else p.z
s += f'Z{z:.6f}'.rstrip('0').rstrip('.') + ' '
return s if s != '' else None

def gcode(self, state):
Expand All @@ -36,7 +40,7 @@ def gcode(self, state):
str: The generated line of gcode.

'''
XYZ_str = self.XYZ_gcode(state.point)
XYZ_str = self.XYZ_gcode(state.point,state.printer.always_print_geometry)
if XYZ_str != None: # only write a line of gcode if movement occurs
G_str = 'G1 ' if state.extruder.on or state.extruder.travel_format == "G1_E0" else 'G0 '
F_str = state.printer.f_gcode(state)
Expand Down
6 changes: 5 additions & 1 deletion fullcontrol/gcode/printer.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ class Printer(BasePrinter):
command_list (Optional[dict]): A dictionary containing the printer's command list.
new_command (Optional[dict]): A dictionary containing a new command to be added to the command list.
speed_changed (Optional[bool]): A flag indicating whether the print speed or travel speed has changed.
always_print_F (Optional[bool]): If True include speed in every gcode line irrespective of wether it changed. Defaults to False.
always_print_geometry (Optional[bool]): If True include x, y and z coordinate in every gcode line irrespective of wether each coordinate changed. Defaults to False.
'''
command_list: Optional[dict] = None
new_command: Optional[dict] = None
speed_changed: Optional[bool] = None
always_print_F: Optional[bool] = None
always_print_geometry: Optional[bool] = None

def f_gcode(self, state):
"""
Expand All @@ -26,7 +30,7 @@ def f_gcode(self, state):
Returns:
- The G-code string for the feedrate (F) based on the current state.
"""
if self.speed_changed == True:
if self.speed_changed == True or self.always_print_F == True:
return f'F{self.print_speed if state.extruder.on else self.travel_speed:.1f}'.rstrip('0').rstrip('.') + ' '
else:
return ''
Expand Down
5 changes: 4 additions & 1 deletion fullcontrol/gcode/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ def __init__(self, steps: list, gcode_controls: GcodeControls):
command_list=initialization_data['printer_command_list'],
print_speed=initialization_data['print_speed'],
travel_speed=initialization_data['travel_speed'],
speed_changed=True)
speed_changed=True,
always_print_F=gcode_controls.always_print_F,
always_print_geometry=gcode_controls.always_print_geometry,
)

self.extrusion_geometry = ExtrusionGeometry(
area_model=initialization_data['area_model'],
Expand Down
2 changes: 2 additions & 0 deletions fullcontrol/visualize/controls.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class PlotControls(BaseModel):
hide_axes (Optional[bool]): Whether to hide axes in the plot. Default is False.
neat_for_publishing (Optional[bool]): Whether to optimize the plot for publishing. Default is False.
raw_data (Optional[bool]): Whether to show raw data in the plot. Default is False.
return_fig (Optional[bool]): Whether to return the plotly figure object (True) or show it with returning (False). Defaults to False to match backwards compatability.
printer_name (Optional[str]): The name of the printer. Default is 'generic'.
initialization_data (Optional[dict]): Information about initial printing conditions. Default is an empty dictionary. Values passed for initialization_data overwrite the default initialization_data of the printer.
"""
Expand All @@ -33,6 +34,7 @@ class PlotControls(BaseModel):
hide_axes: Optional[bool] = False
neat_for_publishing: Optional[bool] = False
raw_data: Optional[bool] = False
return_fig: Optional[bool] = False
printer_name: Optional[str] = 'generic'
# initialization_data is information about initial printing conditions, which may be changed by the fullcontrol 'design', whereas the above attributes are never changed by the 'design'
initialization_data: Optional[dict] = {} # values passed for initialization_data overwrite the default initialization_data of the printer
Expand Down
4 changes: 1 addition & 3 deletions fullcontrol/visualize/plotly.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,7 @@ def plot(data: PlotData, controls: PlotControls):
fig.update_layout(width=500, height=500)

# cicd_testing is a flag set by the CICD testing script (as a temporary environmental variable) to save the plot as a .png file
if not cicd_testing:
fig.show()
else:
if cicd_testing:
import plotly.io as pio
from datetime import datetime
pio.write_image(fig, datetime.now().strftime("figure__%d-%m-%Y__%H-%M-%S.png"))
Expand Down
9 changes: 7 additions & 2 deletions fullcontrol/visualize/steps2visualization.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def visualize(steps: list, plot_controls: PlotControls, show_tips: bool):
- plot_controls (PlotControls, optional): The style of the plot can be adjusted by passing a PlotControls instance.

Returns:
- PlotData if `plot_controls.raw_data` is True, otherwise the Plotly Figure.
- PlotData if `plot_controls.raw_data` is True. Or return Plotly figure object of `plot_controls.return_fig` is True. Otherwise return nothing and show figure (matches legacy behaviour).
'''
plot_controls.initialize()
if show_tips: tips(plot_controls)
Expand All @@ -29,4 +29,9 @@ def visualize(steps: list, plot_controls: PlotControls, show_tips: bool):
return plot_data
else:
from fullcontrol.visualize.plotly import plot
return plot(plot_data, plot_controls)
fig = plot(plot_data, plot_controls)

if plot_controls.return_fig == True:
return fig
else:
fig.show()
44 changes: 44 additions & 0 deletions tutorials/colab/gcode_controls_colab.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
"- `printer_name` - used to choose which printer the gcode should be formatted for\n",
" - a selection of printers are built in, which is expected to be extended. documentation for adding new printers will be provided in the future\n",
"- `initialization_data` - used to change the initial print conditions/settings that are established before the steps in the ***design*** are evaluated\n",
"- `always_print_F` - used to always include speed in every gcode line irrespective of wether it changed. Defaults to False.\n",
"- `always_print_geometry` - used to always include x, y and z coordinate in every gcode line irrespective of wether each coordinate changed. Defaults to False.\n",
"\n",
"<*this document is a jupyter notebook - if they're new to you, check out how they work:\n",
"[link](https://www.google.com/search?q=ipynb+tutorial),\n",
Expand Down Expand Up @@ -348,6 +350,48 @@
"steps = starting_procedure_steps + design_steps\n",
"print(fc.transform(steps, 'gcode', gcode_controls))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Always include speed and geometry"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"initial_settings = {\n",
" \"extrusion_width\": 0.4,\n",
" \"extrusion_height\": 0.2,\n",
" \"primer\": \"no_primer\",\n",
" \"print_speed\": 1000,\n",
"}\n",
"gcode_controls = fc.GcodeControls(initialization_data=initial_settings,printer_name='generic')\n",
"gcode_controls_F = fc.GcodeControls(always_print_F=True,initialization_data=initial_settings,printer_name='generic')\n",
"gcode_controls_geometry = fc.GcodeControls(always_print_geometry=True,initialization_data=initial_settings,printer_name='generic')\n",
"gcode_controls_both = fc.GcodeControls(always_print_F=True,always_print_geometry=True,initialization_data=initial_settings,printer_name='generic')\n",
"steps = [fc.Extruder(on=True),fc.Printer(print_speed=1000),fc.Point(x=0,y=0,z=0),fc.Point(y=1),fc.Point(x=1),fc.Printer(print_speed=2000),fc.Point(y=0),fc.Point(x=0)]\n",
"\n",
"print(\"Conventional gcode:\")\n",
"print(fc.transform(steps, 'gcode', gcode_controls)[175:]) # The initial 175 characters are a comment, and are removed for clarity\n",
"print(\"\")\n",
"\n",
"print(\"With speed always on:\")\n",
"print(fc.transform(steps, 'gcode', gcode_controls_F)[175:])\n",
"print(\"\")\n",
"\n",
"print(\"With geometry always on:\")\n",
"print(fc.transform(steps, 'gcode', gcode_controls_geometry)[175:])\n",
"print(\"\")\n",
"\n",
"print(\"With both always on:\")\n",
"print(fc.transform(steps, 'gcode', gcode_controls_both)[175:])\n",
"print(\"\")"
]
}
],
"metadata": {
Expand Down
24 changes: 24 additions & 0 deletions tutorials/colab/other_functions_colab.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,30 @@
"print(\"last point in the design (fully defined): \" + str(fc.last_point(steps, fully_defined=True)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### find the first or last print point in a ***design*** with fc.first_print_point() and fc.last_print_point() \n",
"\n",
"these functions find the first or last point objects in the design with the extruder being on. It hence disregards travel moves."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"steps = [fc.Extruder(on=False),fc.Point(x=0,y=1,z=0),fc.Point(x=0,y=0,z=0),fc.Extruder(on=True),fc.Point(x=1,y=0,z=0),fc.Extruder(on=False),fc.Point(x=1,y=0,z=1)]\n",
"\n",
"print(\"first step in the design: \" + str(type(steps[0]).__name__))\n",
"print(\"first point in the design: \" + str(fc.first_point(steps)))\n",
"print(\"first print point in the design: \" + str(fc.first_print_point(steps,extruder=False))) # In this example the initial step defines the extruder so setting the extruder to True or False makes no difference.\n",
"print(\"last print point in the design: \" + str(fc.last_print_point(steps,extruder=False)))\n",
"print(\"last point in the design: \" + str(fc.last_point(steps)))"
]
},
{
"attachments": {},
"cell_type": "markdown",
Expand Down
7 changes: 5 additions & 2 deletions tutorials/colab/plot_controls_colab.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"- `hide_axes` - True/False\n",
"- `neat_for_publishing` - True/False (square format for consistent png creation)\n",
"- `raw_data` - True/False (output data instead of creating a plot)\n",
"- `return_fig` - True/False (output figure object instead of showing plot immediatly)\n",
"- `style` - options: 'tube'/'line' - preview 3D real-printed-volume lines or simple lines with non-representative widths\n",
" - if `style == 'tube'`:\n",
" - `tube_type` - options: 'flow'/'cylinders' - adjust how the plot transitions from line to line\n",
Expand Down Expand Up @@ -313,7 +314,7 @@
"source": [
"#### edit the returned figure\n",
"\n",
"`fc.transform` displays and returns a Plotly figure, which can be edited and shown again with updated formatting"
"`fc.transform` displays or returns a Plotly figure depending on `plot_controls.return_fig`. If `return_fig` is True it can be edited and shown again with updated formatting."
]
},
{
Expand All @@ -323,7 +324,9 @@
"outputs": [],
"source": [
"print('Original figure:')\n",
"fig = fc.transform(steps, 'plot', show_tips=False)\n",
"plot_controls = fc.PlotControls(return_fig=True)\n",
"fig = fc.transform(steps, 'plot', plot_controls, show_tips=False)\n",
"fig.show()\n",
"\n",
"# Update the template and override the explicitly set background colors.\n",
"fig.update_layout(\n",
Expand Down
Loading