bgcode support linux
This commit is contained in:
parent
e155b26171
commit
de353e7d04
7 changed files with 106 additions and 10 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
from subprocess import CompletedProcess
|
||||||
import tempfile
|
import tempfile
|
||||||
import shlex
|
import shlex
|
||||||
import shutil
|
import shutil
|
||||||
|
|
@ -11,6 +12,7 @@ import bpy
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from mathutils import Euler
|
from mathutils import Euler
|
||||||
|
|
||||||
|
|
||||||
from .bpy_ns_types import NfIntProperty, NfStringProperty, NfEnumProperty, NfBoolProperty, notify_tree_changed
|
from .bpy_ns_types import NfIntProperty, NfStringProperty, NfEnumProperty, NfBoolProperty, notify_tree_changed
|
||||||
|
|
||||||
from ...registry import register_class
|
from ...registry import register_class
|
||||||
|
|
@ -19,6 +21,7 @@ from ..slice import Pather, slice_at_range, SlicePaths
|
||||||
from ...infra.blender.blender_mesh_capture import SlicingCollection, SlicingObject, capture_mesh_edges, SlicingGroup
|
from ...infra.blender.blender_mesh_capture import SlicingCollection, SlicingObject, capture_mesh_edges, SlicingGroup
|
||||||
from ...infra.blender.blender_bridge import get_all_children
|
from ...infra.blender.blender_bridge import get_all_children
|
||||||
from ...infra.blender.blender_slice_nodes_generic import SliceNode, resolve_input
|
from ...infra.blender.blender_slice_nodes_generic import SliceNode, resolve_input
|
||||||
|
from ...infra.prusaslicer_profiles import ConfigWriter
|
||||||
from ...infra.prusaslicer_profiles.cache import LocalCache
|
from ...infra.prusaslicer_profiles.cache import LocalCache
|
||||||
from ...infra.prusaslicer_profiles.models import Profile
|
from ...infra.prusaslicer_profiles.models import Profile
|
||||||
from ...infra.networking.airtable_backend import AirtableRecord
|
from ...infra.networking.airtable_backend import AirtableRecord
|
||||||
|
|
@ -968,7 +971,6 @@ class SLICENODE_PrinterList(SliceNode):
|
||||||
@register_class
|
@register_class
|
||||||
class SLICENODE_GcodeToFile(ExecSliceNode):
|
class SLICENODE_GcodeToFile(ExecSliceNode):
|
||||||
bl_label = "Gcode To File"
|
bl_label = "Gcode To File"
|
||||||
|
|
||||||
button_label = "Generate File"
|
button_label = "Generate File"
|
||||||
|
|
||||||
def init(self, context):
|
def init(self, context):
|
||||||
|
|
@ -976,17 +978,49 @@ class SLICENODE_GcodeToFile(ExecSliceNode):
|
||||||
self.add_input("SLICESOCK_Gcode")
|
self.add_input("SLICESOCK_Gcode")
|
||||||
self.add_input("SLICESOCK_FilePath", "Filename")
|
self.add_input("SLICESOCK_FilePath", "Filename")
|
||||||
|
|
||||||
|
def draw_buttons(self, context, layout):
|
||||||
|
super().draw_buttons(context, layout)
|
||||||
|
|
||||||
def evaluate(self, operator=None):
|
def evaluate(self, operator=None):
|
||||||
|
from ...infra.networking.bgcode import convert_to_bgcode
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
gcode: str = resolve_input(self.inputs.get("Gcode"))
|
gcode: str = resolve_input(self.inputs.get("Gcode"))
|
||||||
filename: str = resolve_input(self.inputs.get("Filename"))
|
filename: str = resolve_input(self.inputs.get("Filename"))
|
||||||
|
if not gcode:
|
||||||
|
if operator: operator.report({'ERROR'}, "No Gcode to save")
|
||||||
|
return 'error'
|
||||||
|
if not filename:
|
||||||
|
if operator: operator.report({'ERROR'}, "No Filename provided")
|
||||||
|
return 'error'
|
||||||
|
|
||||||
filepath = bpy.path.abspath(filename)
|
# User's requested logic: .split('.')[0]
|
||||||
os.makedirs(os.path.dirname(filepath) or ".", exist_ok=True)
|
filepath_abs = bpy.path.abspath(filename)
|
||||||
|
dirname = os.path.dirname(filepath_abs)
|
||||||
|
basename = os.path.basename(filepath_abs)
|
||||||
|
name_part = basename.split('.')[0]
|
||||||
|
os.makedirs(dirname, exist_ok=True)
|
||||||
|
|
||||||
with open(filepath, "w", encoding="utf-8", newline="\n") as f:
|
# Detect binary trigger in gcode comments
|
||||||
f.write(gcode)
|
# Looking for "; binary_gcode = 1"
|
||||||
|
is_binary = "; binary_gcode = 1" in gcode
|
||||||
|
|
||||||
return 'ok'
|
try:
|
||||||
|
if is_binary:
|
||||||
|
filepath = os.path.join(dirname, name_part + ".bgcode")
|
||||||
|
content = convert_to_bgcode(gcode, name_part, True)
|
||||||
|
with open(filepath, "wb") as f:
|
||||||
|
f.write(content)
|
||||||
|
if operator: operator.report({'INFO'}, f"Binary G-code generated at {filepath}")
|
||||||
|
else:
|
||||||
|
filepath = os.path.join(dirname, name_part + ".gcode")
|
||||||
|
with open(filepath, "w", encoding="utf-8", newline="\n") as f:
|
||||||
|
f.write(gcode)
|
||||||
|
if operator: operator.report({'INFO'}, f"G-code saved to {filepath}")
|
||||||
|
return 'ok'
|
||||||
|
except Exception as e:
|
||||||
|
if operator: operator.report({'ERROR'}, str(e))
|
||||||
|
return 'error'
|
||||||
|
|
||||||
@register_class
|
@register_class
|
||||||
class SLICENODE_GcodeToTextBlock(ExecSliceNode):
|
class SLICENODE_GcodeToTextBlock(ExecSliceNode):
|
||||||
|
|
@ -1147,12 +1181,19 @@ class ExecSliceNode_DatabaseSubmit(ExecSliceNode):
|
||||||
return gcode, filename, fields
|
return gcode, filename, fields
|
||||||
|
|
||||||
def evaluate(self, operator=None):
|
def evaluate(self, operator=None):
|
||||||
|
from ...infra.networking.bgcode import convert_to_bgcode
|
||||||
import tempfile
|
import tempfile
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
gcode, filename, fields = self._prepare_submission(operator)
|
gcode, filename, fields = self._prepare_submission(operator)
|
||||||
if not gcode: return 'error'
|
if not gcode: return 'error'
|
||||||
|
|
||||||
|
# Detect binary trigger in gcode comments
|
||||||
|
is_binary = "; binary_gcode = 1" in gcode
|
||||||
|
|
||||||
|
name_part = filename.split('.')[0]
|
||||||
|
filename = name_part + ".bgcode" if is_binary else ".gcode"
|
||||||
|
|
||||||
token_path = getattr(self, self._token_prop_name, "")
|
token_path = getattr(self, self._token_prop_name, "")
|
||||||
token = ""
|
token = ""
|
||||||
|
|
@ -1172,10 +1213,16 @@ class ExecSliceNode_DatabaseSubmit(ExecSliceNode):
|
||||||
|
|
||||||
temp_dir = tempfile.TemporaryDirectory()
|
temp_dir = tempfile.TemporaryDirectory()
|
||||||
temp_path = Path(temp_dir.name) / filename
|
temp_path = Path(temp_dir.name) / filename
|
||||||
with open(temp_path, 'w', encoding='utf-8') as f:
|
|
||||||
f.write(gcode)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if is_binary:
|
||||||
|
content = convert_to_bgcode(gcode, name_part, True)
|
||||||
|
with open(temp_path, "wb") as f:
|
||||||
|
f.write(content)
|
||||||
|
else:
|
||||||
|
with open(temp_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(gcode)
|
||||||
|
|
||||||
record_id = self._submit_to_db(token, fields, temp_path)
|
record_id = self._submit_to_db(token, fields, temp_path)
|
||||||
if operator: operator.report({'INFO'}, f"Submitted to {self._db_name} (Record: {record_id})")
|
if operator: operator.report({'INFO'}, f"Submitted to {self._db_name} (Record: {record_id})")
|
||||||
return 'ok'
|
return 'ok'
|
||||||
|
|
@ -1340,16 +1387,18 @@ class SLICENODE_PrusaslicerSlice(ExecSliceNode):
|
||||||
# Prepare overrides for generate_conf_writer
|
# Prepare overrides for generate_conf_writer
|
||||||
# The format expected by generate_conf_writer is overrides={'key': {'value': 'val'}}
|
# The format expected by generate_conf_writer is overrides={'key': {'value': 'val'}}
|
||||||
writer_overrides = {k: {'value': v} for k, v in overrides.items()}
|
writer_overrides = {k: {'value': v} for k, v in overrides.items()}
|
||||||
writer_overrides['binary_gcode'] = {'value': '0'}
|
|
||||||
|
|
||||||
# Generate the ConfigWriter
|
# Generate the ConfigWriter
|
||||||
conf_writer = cache.generate_conf_writer(
|
conf_writer: ConfigWriter = cache.generate_conf_writer(
|
||||||
printer_id,
|
printer_id,
|
||||||
filament_ids,
|
filament_ids,
|
||||||
print_id,
|
print_id,
|
||||||
overrides=writer_overrides
|
overrides=writer_overrides
|
||||||
)
|
)
|
||||||
|
|
||||||
|
binary_gcode_value = conf_writer.config_dict['binary_gcode']
|
||||||
|
conf_writer.config_dict['binary_gcode'] = '0'
|
||||||
|
|
||||||
# Prepare 3mf path
|
# Prepare 3mf path
|
||||||
temp_dir = Path(tempfile.gettempdir())
|
temp_dir = Path(tempfile.gettempdir())
|
||||||
output_path = temp_dir / f"tmp.3mf"
|
output_path = temp_dir / f"tmp.3mf"
|
||||||
|
|
@ -1397,6 +1446,7 @@ class SLICENODE_PrusaslicerSlice(ExecSliceNode):
|
||||||
if gcode_path.exists():
|
if gcode_path.exists():
|
||||||
with open(gcode_path, 'r', encoding='utf-8') as f:
|
with open(gcode_path, 'r', encoding='utf-8') as f:
|
||||||
self.gcode_result = f.read()
|
self.gcode_result = f.read()
|
||||||
|
self.gcode_result += f"\n; binary_gcode = {binary_gcode_value}\n"
|
||||||
|
|
||||||
output_path.unlink()
|
output_path.unlink()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ All nodes are found in the **Add (Shift+A)** menu under the **SliceNodes** categ
|
||||||
- **Header**: Toggles the generation of standard NodeSlicer G-code headers and units setup.
|
- **Header**: Toggles the generation of standard NodeSlicer G-code headers and units setup.
|
||||||
- **Load Gcode**: Reads a G-code file from disk.
|
- **Load Gcode**: Reads a G-code file from disk.
|
||||||
- **Gcode to File**: Writes G-code string to a file on disk.
|
- **Gcode to File**: Writes G-code string to a file on disk.
|
||||||
|
- **Automatic Binary Conversion**: If the G-code contains the comment `; binary_gcode = 1`, the node automatically converts the output to Prusa's binary G-code format (`.bgcode`) using MeatPack encoding and Heatshrink compression.
|
||||||
- **Baserow Submit**: Sends G-code and metadata to a Baserow database.
|
- **Baserow Submit**: Sends G-code and metadata to a Baserow database.
|
||||||
- **Host**: Baserow host URL.
|
- **Host**: Baserow host URL.
|
||||||
- **Token File**: Path to a file containing the Baserow API token.
|
- **Token File**: Path to a file containing the Baserow API token.
|
||||||
|
|
|
||||||
BIN
gcode_snippets/mk3s_header.bgcode
Normal file
BIN
gcode_snippets/mk3s_header.bgcode
Normal file
Binary file not shown.
BIN
infra/bin/bgcode
Executable file
BIN
infra/bin/bgcode
Executable file
Binary file not shown.
41
infra/networking/bgcode.py
Normal file
41
infra/networking/bgcode.py
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def convert_to_bgcode(gcode: str, name_part: str, compress: bool = True) -> bytes:
|
||||||
|
"""
|
||||||
|
Converts a G-code string to binary BGCode using the internal bgcode tool.
|
||||||
|
Returns the binary content of the converted file.
|
||||||
|
"""
|
||||||
|
addon_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
bgcode_bin = os.path.join(addon_dir, "infra", "bin", "bgcode")
|
||||||
|
|
||||||
|
if not os.path.exists(bgcode_bin):
|
||||||
|
raise FileNotFoundError(f"bgcode binary not found at {bgcode_bin}")
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
tmp_gcode = Path(tmpdir) / (name_part + ".gcode")
|
||||||
|
tmp_gcode.write_text(gcode, encoding="utf-8")
|
||||||
|
|
||||||
|
# Run conversion: bgcode <input>
|
||||||
|
cmd = [bgcode_bin, str(tmp_gcode)]
|
||||||
|
if compress:
|
||||||
|
# Recommended settings for true binary bgcode
|
||||||
|
cmd.extend([
|
||||||
|
"--slicer_metadata_compression=1",
|
||||||
|
"--gcode_compression=3",
|
||||||
|
"--gcode_encoding=2"
|
||||||
|
])
|
||||||
|
|
||||||
|
res = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
if res.returncode != 0:
|
||||||
|
err_msg = res.stderr.strip() or res.stdout.strip() or "Unknown error"
|
||||||
|
raise RuntimeError(f"bgcode conversion failed: {err_msg}")
|
||||||
|
|
||||||
|
tmp_bgcode = tmp_gcode.with_suffix(".bgcode")
|
||||||
|
if tmp_bgcode.exists():
|
||||||
|
return tmp_bgcode.read_bytes()
|
||||||
|
else:
|
||||||
|
raise FileNotFoundError("Failed to find generated .bgcode file")
|
||||||
0
test.bgcode
Normal file
0
test.bgcode
Normal file
4
test.gcode
Normal file
4
test.gcode
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
; printer_model = MK3S
|
||||||
|
; printer_notes =
|
||||||
|
; total filament used [g] = 0
|
||||||
|
G1 X10
|
||||||
Loading…
Add table
Reference in a new issue