Skip to content

Commit

Permalink
SW-3809 - Backend- Select materials csv based on laser cutter mode (#…
Browse files Browse the repository at this point in the history
…1815)

* Select materials csv based on laser cutter mode

* Rework custom materials logic

* Add unit tests

* Pass laser cutter mode to materials

* Update octoprint_mrbeam/util/material_csv_parser.py

Co-authored-by: khaledsherkawi <[email protected]>

* Update octoprint_mrbeam/util/material_csv_parser.py

Co-authored-by: khaledsherkawi <[email protected]>

* Update octoprint_mrbeam/materials.py

Co-authored-by: khaledsherkawi <[email protected]>

* Improve method name

* Add test for empty custom materials

---------

Co-authored-by: khaledsherkawi <[email protected]>
  • Loading branch information
irlaec and khaledsherkawi authored Oct 6, 2023
1 parent 0d88f51 commit db245dc
Show file tree
Hide file tree
Showing 9 changed files with 375 additions and 240 deletions.
218 changes: 109 additions & 109 deletions octoprint_mrbeam/__init__.py

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions octoprint_mrbeam/analytics/analytics_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1430,9 +1430,7 @@ def _add_event_to_queue(
AnalyticsKeys.Header.LH_MODEL_ID: self._laserhead_handler.get_current_used_lh_model_id()
if self._laserhead_handler is not None
else self.UNKNOWN_VALUE,
AnalyticsKeys.Header.LH_SERIAL: self._laserhead_handler.get_current_used_lh_data()[
"serial"
]
AnalyticsKeys.Header.LH_SERIAL: self._plugin.get_current_laser_head_serial()
if self._laserhead_handler is not None
else self.UNKNOWN_VALUE,
AnalyticsKeys.Header.FEATURE_ID: header_extension.get(
Expand Down
17 changes: 17 additions & 0 deletions octoprint_mrbeam/files/material_settings/materials_rotary.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Laserhead,Material,Color,Thickness,Intensity,Speed,Passes,Compressor,Piercing time,Dithering,Comments
You can drag the cell to fill a range with the same name,Color codes take a long time to update -->,"Use ""fill color"" tool",Engrave or (mm),light-dark (%),light-dark 0 to 1500,,level 0 to 3,(ms),yes/no,"interpolated value or #madewithmrbeam? Let us know what makes you tick
"
Mr Beam II,Bamboo,#e7c982,Engrave,0-100,1500-450,1,0,0,no,values from default material settings
Mr Beam II,Cork,#c19c73,Engrave,0-30,1500-1400,1,0,0,no,values from default material settings
,,,,,,,,,,
,DREAMCUT,,,,,,,,,
MrB II Dreamcut,Bamboo,#e7c982,Engrave,0-50,1500-1500,1,3,0,no,values from default material settings
MrB II Dreamcut,Cork,#c19c73,Engrave,0-30,1500-1500,1,3,0,no,values from default material settings
,,,,,,,,,,
,DREAMCUT S,,,,,,,,,
MrB II Dreamcut S,Bamboo,#e7c982,Engrave,0-50,2000-2000,1,3,0,no,values from default material settings
MrB II Dreamcut S,Cork,#c19c73,Engrave,0-15,1500-1500,1,3,0,no,values from default material settings
,,,,,,,,,,
,DREAMCUT X,,,,,,,,,
MrB II Dreamcut x,Bamboo,#e7c982,Engrave,0-30,2000-2000,1,3,0,no,values from default material settings
MrB II Dreamcut x,Cork,#c19c73,Engrave,0-14,2000-2000,1,3,0,no,values from default material settings
186 changes: 60 additions & 126 deletions octoprint_mrbeam/materials.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import os
import yaml
from octoprint_mrbeam.mrb_logger import mrb_logger
from octoprint_mrbeam.util import dict_get

from octoprint_mrbeam.model.custom_material_model import CustomMaterialModel
from octoprint_mrbeam.mrb_logger import mrb_logger

# singleton
_instance = None

# TODO: SW-3719 import these from mode services
DEFAULT_MODE = "default"
ROTARY_MODE = "rotary"


def materials(plugin):
global _instance
Expand All @@ -16,7 +20,6 @@ def materials(plugin):


class Materials(object):

FILE_CUSTOM_MATERIALS = "materials.yaml"

def __init__(self, plugin):
Expand All @@ -26,122 +29,24 @@ def __init__(self, plugin):
self.plugin._settings.getBaseFolder("base"), self.FILE_CUSTOM_MATERIALS
)

self.custom_materials = dict()
self.custom_materials_loaded = False

def get_custom_materials(self):
"""
Get list of currently saved custom materials
:return:
"""
self._load()
return self.custom_materials

def put_custom_material(self, key, material):
"""Sanitize and put material. If key exists, material will be
overwritten.
self.custom_materials = self._load_materials_from_yaml()

:param key: String unique material key
:param material: Dict of material data
:return: Boolean success
"""
self._load()
res = None
def _load_materials_from_yaml(self):
custom_materials = {}

try:
if dict_get(material, ['laser_type']) == "MrBeamII-1.0":
material["laser_model"] = '0'
del material["laser_type"]
if "model" in material:
material["device_model"] = material.pop("model")
if "compatible" in material:
material.pop("compatible")
if "customBeforeElementContent" in material:
material.pop("customBeforeElementContent")

self.custom_materials[key.strip()] = material
res = True
except:
self._logger.exception(
"Exception while putting materials: key: %s, data: %s", key, material
)
res = False
if res:
res = self._save()
return res
if os.path.isfile(self.custom_materials_file):
with open(self.custom_materials_file) as yaml_file:
content = yaml.safe_load(yaml_file)
custom_materials = content.get("custom_materials", {})

def delete_custom_material(self, key):
"""Deletes custom material if existing.
self._logger.info("{} custom materials loaded".format(len(custom_materials)))
except Exception as e:
self._logger.exception("Exception while loading custom materials: {}".format(e))

:param keys: String or list: key or list of keys to delete
:return: Boolean success
"""
self._load()
count = 0
res = True

key_list = key
if isinstance(key_list, basestring):
key_list = [key_list]

if key_list:
try:
for k in key_list:
try:
del self.custom_materials[k]
count += 1
except ValueError:
pass
except:
self._logger.exception(
"Exception while deleting materials: key: %s", key
)
res = False
if res and count > 0:
res = self._save()
return res
return custom_materials

def reset_all_custom_materials(self):
self._logger.info("Resetting all custom material settings!!!!")
self.custom_materials = {}
self._save(force=True)

def _load(self, force=False):
if not self.custom_materials_loaded or force:
try:
if os.path.isfile(self.custom_materials_file):
with open(self.custom_materials_file) as yaml_file:
tmp = yaml.safe_load(yaml_file)
self.custom_materials = (
tmp["custom_materials"]
if tmp and "custom_materials" in tmp
else dict()
)
self._logger.debug(
"Loaded %s custom materials from file %s",
len(self.custom_materials),
self.custom_materials_file,
)
else:
self.custom_materials = dict()
self._logger.debug(
"No custom materials yet. File %s does not exist.",
self.custom_materials_file,
)
self.custom_materials_loaded = True
except Exception as e:
self._logger.exception(
"Exception while loading custom materials from file {}".format(
self.custom_materials_file
)
)
self.custom_materials = dict()
self.custom_materials_loaded = False
return self.custom_materials

def _save(self, force=False):
if not self.custom_materials_loaded and not force:
raise Exception("You need to load custom_materials before trying to save.")
def _write_materials_to_yaml(self):
try:
data = dict(custom_materials=self.custom_materials)
with open(self.custom_materials_file, "wb") as new_yaml:
Expand All @@ -152,17 +57,46 @@ def _save(self, force=False):
indent=" ",
allow_unicode=True,
)
self.custom_materials_loaded = True
self._logger.debug(
"Saved %s custom materials (in total) to file %s",
len(self.custom_materials),
self.custom_materials_file,
)
except:
self._logger.exception(
"Exception while writing custom materials to file %s",
self.custom_materials_file,
self._logger.info("{} custom materials saved".format(len(self.custom_materials)))
except Exception as e:
self._logger.exception("Exception while saving custom materials: {}".format(e))

def get_custom_materials_for_laser_cutter_mode(self):
"""Get currently saved custom materials for a specific laser cutter mode.
If a material doesn't have a laser_cutter_mode set, default will be assumed.
Returns: The list of custom materials for the given laser cutter mode
"""
laser_cutter_mode = self.plugin.get_laser_cutter_mode()
return {
key: value for key, value in self.custom_materials.items() if
value.get('laser_cutter_mode', DEFAULT_MODE) == laser_cutter_mode
}

def add_custom_material(self, material_key, material):
try:
self._logger.info("Adding custom material: {}".format(material.get("name")))
custom_material = CustomMaterialModel(
material_key=material_key,
material=material,
laser_cutter_mode=self.plugin.get_laser_cutter_mode(),
laser_model=self.plugin.get_current_laser_head_model(),
plugin_v=self.plugin.get_plugin_version(),
device_model=self.plugin.get_model_id()
)
self.custom_materials_loaded = False
return False
return True
self.custom_materials[custom_material.material_key] = custom_material.to_dict()
self._write_materials_to_yaml()
except Exception as e:
self._logger.exception("Exception while adding material: {}".format(e))

def delete_custom_material(self, material_key):
if material_key in self.custom_materials:
self._logger.info("Deleting custom material: {}".format(material_key))
del self.custom_materials[material_key]
self._write_materials_to_yaml()

def delete_all_custom_materials(self):
self._logger.info("Deleting all custom materials")
self.custom_materials = {}
self._write_materials_to_yaml()
57 changes: 57 additions & 0 deletions octoprint_mrbeam/model/custom_material_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

class CustomMaterialModel:
def __init__(self, material_key, material, laser_cutter_mode, laser_model, plugin_v, device_model):
self.colors = material.get("colors", [])
self.custom = True
self.description = material.get("description", "")
self.device_model = device_model
self.hints = material.get("hints", "")
self.img = material.get("img", "null")
self.laser_cutter_mode = laser_cutter_mode
self.laser_model = laser_model
self.name = material.get("name")
self.safety_notes = material.get("safety_notes", "")
self.v = plugin_v

self.material_key = self.generate_material_key(material_key)

def __str__(self):
return "CustomMaterialModel(name='{0}', description='{1}')".format(self.name, self.description)

def __repr__(self):
return (
"CustomMaterialModel("
"key='{0}', "
"colors={1}, "
"custom={2}, "
"description='{3}', "
"device_model='{4}', "
"hints='{5}', "
"img='{6}', "
"laser_cutter_mode='{7}', "
"laser_model='{8}', "
"name='{9}', "
"safety_notes='{10}', "
"v={11})"
.format(
self.material_key,
self.colors,
self.custom,
self.description,
self.device_model,
self.hints,
self.img,
self.laser_cutter_mode,
self.laser_model,
self.name,
self.safety_notes,
self.v
)
)

def to_dict(self):
material = vars(self)
return material

def generate_material_key(self, material_key):
return "{} - {}".format(material_key, self.laser_cutter_mode)
20 changes: 18 additions & 2 deletions octoprint_mrbeam/util/material_csv_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@

VALID_MODELS = [MRBEAM, MRB_DREAMCUT, MRB_DREAMCUT_S, MRB_READY, MRB_DREAMCUT_X]

# TODO: import these from mode services
DEFAULT_MODE = "default"
ROTARY_MODE = "rotary"

MATERIALS_CSV_DIR = "files/material_settings/"
DEFAULT_MATERIALS_CSV_DIR = MATERIALS_CSV_DIR + "materials.csv"
ROTARY_MATERIALS_CSV_DIR = MATERIALS_CSV_DIR + "materials_rotary.csv"
MATERIALS_CSVS = {
DEFAULT_MODE: DEFAULT_MATERIALS_CSV_DIR,
ROTARY_MODE: ROTARY_MATERIALS_CSV_DIR,
}


def model_ids_to_csv_name(device_model_id, laser_model_id):
convert = {
Expand Down Expand Up @@ -63,17 +75,21 @@ def dict_merge(dct, merge_dct):
dct[k] = merge_dct[k]


def parse_csv(path=None, device_model=MRBEAM, laserhead_model="0"):
def parse_csv(path=None, device_model=MRBEAM, laserhead_model="0", laser_cutter_mode=DEFAULT_MODE):
"""Assumes following column order: mrbeamversion, material, colorcode,
thickness_or_engrave, intensity, speed, passes, pierce_time, dithering.
:param path: path to csv file
:param device_model: the model of the device to use. Will return the material settings to use for that model.
:param laserhead_model: the type of laserhead to use. Will return the material settings to use for that laserhead.
:param laser_cutter_mode: the laser cutter mode. Will return the material settings to use for that laser cutter mode.
:return:
"""

csv_path = MATERIALS_CSVS[laser_cutter_mode]

path = path or os.path.join(
__package_path__, "files/material_settings/materials.csv"
__package_path__, csv_path
)
dictionary = {}
with open(path, "r") as f:
Expand Down
34 changes: 34 additions & 0 deletions tests/model/test_custom_material_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import pytest

from octoprint_mrbeam.model.custom_material_model import CustomMaterialModel


@pytest.fixture
def sample_custom_material():
sample_material_data = {
"name": "Sample Material",
"description": "This is a test material",
"colors": ["red", "green", "blue"],
"hints": "Some hints",
"img": "sample.jpg",
"laser_cutter_mode": "default",
"laser_model": "sample_model",
"safety_notes": "Safety notes for testing",
"plugin_v": "1.0",
"device_model": "Device123",
}

custom_material = CustomMaterialModel("sample_key", sample_material_data, "default", "X", "1.0", "dreamcut")

yield custom_material


def test_material_key_generation(sample_custom_material):
assert sample_custom_material.material_key == "sample_key - default"


def test_to_dict(sample_custom_material):
material_dict = sample_custom_material.to_dict()
expected_dict = {'laser_cutter_mode': 'default', 'safety_notes': 'Safety notes for testing', 'description': 'This is a test material', 'img': 'sample.jpg', 'device_model': 'dreamcut', 'name': 'Sample Material', 'custom': True, 'laser_model': 'X', 'colors': ['red', 'green', 'blue'], 'v': '1.0', 'material_key': 'sample_key - default', 'hints': 'Some hints'}

assert material_dict == expected_dict
Loading

0 comments on commit db245dc

Please sign in to comment.