xml
This commit is contained in:
parent
bedc33c7d3
commit
ec539fa9fe
4 changed files with 85 additions and 53 deletions
|
|
@ -534,7 +534,7 @@ class SLICENODE_TransformGeometry(SliceNode):
|
||||||
self.add_output("SLICESOCK_Geometry", "Geometry")
|
self.add_output("SLICESOCK_Geometry", "Geometry")
|
||||||
|
|
||||||
def evaluate_output(self, socket_name: str, **_kwargs) -> Any:
|
def evaluate_output(self, socket_name: str, **_kwargs) -> Any:
|
||||||
if socket_name not in ("Geometry"):
|
if socket_name not in ("Geometry",):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
geom_data: Any = self.resolve("Geometry")
|
geom_data: Any = self.resolve("Geometry")
|
||||||
|
|
@ -835,6 +835,3 @@ class SLICENODE_AssignPropertyToObject(SliceNode):
|
||||||
props.append(("", str(item)))
|
props.append(("", str(item)))
|
||||||
|
|
||||||
return _assign_properties_to_item(geom, props)
|
return _assign_properties_to_item(geom, props)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from numpy.typing import NDArray
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import os, shutil, tempfile
|
import os, shutil, tempfile
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from ..blender.blender_mesh_capture import SlicingObject, SlicingGroup, SlicingCollection
|
from ..blender.blender_mesh_capture import SlicingGroup, SlicingCollection
|
||||||
|
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
def write_metadata_xml(group: SlicingGroup, filepath):
|
|
||||||
|
def write_metadata_xml(group: SlicingGroup, filepath: str | Path) -> None:
|
||||||
# Custom sorting order for object types
|
# Custom sorting order for object types
|
||||||
object_type_order = {
|
object_type_order = {
|
||||||
'ModelPart': 0,
|
'ModelPart': 0,
|
||||||
|
|
@ -28,23 +28,31 @@ def write_metadata_xml(group: SlicingGroup, filepath):
|
||||||
|
|
||||||
for j, (k, collection) in enumerate(valid_collections.items()):
|
for j, (k, collection) in enumerate(valid_collections.items()):
|
||||||
|
|
||||||
sorted_data: list[tuple[NDArray[Any], NDArray[Any], SlicingObject]] = sorted(zip(collection.mesh_start_ids, collection.mesh_end_ids, collection.objects), key=lambda x: object_type_order.get(x[2].object_type, 5))
|
sorted_data = sorted(
|
||||||
|
zip(collection.mesh_start_ids, collection.mesh_end_ids, collection.objects),
|
||||||
|
key=lambda x: (
|
||||||
|
0 if x[2].name == collection.name else 1,
|
||||||
|
object_type_order.get(x[2].object_type, 5),
|
||||||
|
x[2].name,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
object_elem = ET.SubElement(xml_content, "object", id=str(j+1), instances_count="1")
|
object_elem = ET.SubElement(xml_content, "object", id=str(j+1), instances_count="1")
|
||||||
ET.SubElement(object_elem, "metadata", type="object", key="name", value=collection.name)
|
ET.SubElement(object_elem, "metadata", type="object", key="name", value=collection.name)
|
||||||
|
|
||||||
for i, (start, end, metadata) in enumerate(sorted_data):
|
for i, (start, end, metadata) in enumerate(sorted_data):
|
||||||
if metadata.object_type == "ModelPart":
|
|
||||||
for mod in metadata.modifiers:
|
|
||||||
ET.SubElement(object_elem, "metadata", type="object", key=mod['param_id'], value=mod['param_value'])
|
|
||||||
|
|
||||||
volume_elem = ET.SubElement(object_elem, "volume", firstid=str(start), lastid=str(end))
|
volume_elem = ET.SubElement(object_elem, "volume", firstid=str(start), lastid=str(end))
|
||||||
|
|
||||||
ET.SubElement(volume_elem, "metadata", type="volume", key="name", value=metadata.name)
|
ET.SubElement(volume_elem, "metadata", type="volume", key="name", value=metadata.name)
|
||||||
|
|
||||||
if metadata.object_type == "ParameterModifier":
|
if metadata.object_type == "ParameterModifier":
|
||||||
ET.SubElement(volume_elem, "metadata", type="volume", key="modifier", value="1")
|
ET.SubElement(volume_elem, "metadata", type="volume", key="modifier", value="1")
|
||||||
|
|
||||||
|
if metadata.object_type in ["ModelPart", "ParameterModifier"]:
|
||||||
for mod in metadata.modifiers:
|
for mod in metadata.modifiers:
|
||||||
|
if not mod: continue
|
||||||
ET.SubElement(volume_elem, "metadata", type="volume", key=mod['param_id'], value=mod['param_value'])
|
ET.SubElement(volume_elem, "metadata", type="volume", key=mod['param_id'], value=mod['param_value'])
|
||||||
|
|
||||||
ET.SubElement(volume_elem, "metadata", type="volume", key="volume_type", value=metadata.object_type)
|
ET.SubElement(volume_elem, "metadata", type="volume", key="volume_type", value=metadata.object_type)
|
||||||
ET.SubElement(volume_elem, "metadata", type="volume", key="extruder", value=str(metadata.extruder))
|
ET.SubElement(volume_elem, "metadata", type="volume", key="extruder", value=str(metadata.extruder))
|
||||||
ET.SubElement(volume_elem, "metadata", type="volume", key="source_object_id", value="0")
|
ET.SubElement(volume_elem, "metadata", type="volume", key="source_object_id", value="0")
|
||||||
|
|
@ -56,19 +64,21 @@ def write_metadata_xml(group: SlicingGroup, filepath):
|
||||||
xml_tree = ET.ElementTree(xml_content)
|
xml_tree = ET.ElementTree(xml_content)
|
||||||
xml_tree.write(filepath, encoding="UTF-8", xml_declaration=True)
|
xml_tree.write(filepath, encoding="UTF-8", xml_declaration=True)
|
||||||
|
|
||||||
def write_wipe_tower_xml(group: SlicingGroup, filename):
|
|
||||||
|
def write_wipe_tower_xml(group: SlicingGroup, filename: str | Path) -> None:
|
||||||
with open(filename, 'w', encoding="UTF-8") as file:
|
with open(filename, 'w', encoding="UTF-8") as file:
|
||||||
file.write(f'<?xml version="1.0" encoding="utf-8"?>\n')
|
file.write('<?xml version="1.0" encoding="utf-8"?>\n')
|
||||||
file.write(f'<wipe_tower_information bed_idx="0" position_x="{group.wipe_tower_xy[0]}" position_y="{group.wipe_tower_xy[1]}" rotation_deg="{group.wipe_tower_rotation_deg}"/>\n')
|
file.write(f'<wipe_tower_information bed_idx="0" position_x="{group.wipe_tower_xy[0]}" position_y="{group.wipe_tower_xy[1]}" rotation_deg="{group.wipe_tower_rotation_deg}"/>\n')
|
||||||
|
|
||||||
def write_model_xml(group: SlicingGroup, filename: str):
|
|
||||||
|
def write_model_xml(group: SlicingGroup, filename: str | Path) -> None:
|
||||||
now = date.today().isoformat()
|
now = date.today().isoformat()
|
||||||
|
|
||||||
# Open file for writing
|
# Open file for writing
|
||||||
with open(filename, 'w', encoding="UTF-8") as file:
|
with open(filename, 'w', encoding="UTF-8") as file:
|
||||||
# Write the XML declaration and opening model tag
|
# Write the XML declaration and opening model tag
|
||||||
file.write(f'<?xml version="1.0" encoding="UTF-8"?>\n')
|
file.write('<?xml version="1.0" encoding="UTF-8"?>\n')
|
||||||
file.write(f'<model xmlns="" unit="millimeter" xml:lang="en-US" xmlns:slic3rpe="">\n')
|
file.write('<model xmlns="" unit="millimeter" xml:lang="en-US" xmlns:slic3rpe="">\n')
|
||||||
|
|
||||||
# Write metadata entries using list comprehension
|
# Write metadata entries using list comprehension
|
||||||
metadata_entries: list[tuple[str, str]] = [
|
metadata_entries: list[tuple[str, str]] = [
|
||||||
|
|
@ -86,7 +96,7 @@ def write_model_xml(group: SlicingGroup, filename: str):
|
||||||
file.writelines([f' <metadata name="{name}">{value}</metadata>\n' for name, value in metadata_entries])
|
file.writelines([f' <metadata name="{name}">{value}</metadata>\n' for name, value in metadata_entries])
|
||||||
|
|
||||||
# Write resources element and object using list comprehension
|
# Write resources element and object using list comprehension
|
||||||
file.write(f' <resources>\n')
|
file.write(' <resources>\n')
|
||||||
|
|
||||||
verts_template = np.vectorize(lambda x, y, z: '<vertex x="%.6f" y="%.6f" z="%.6f" />\n' % (x, y, z))
|
verts_template = np.vectorize(lambda x, y, z: '<vertex x="%.6f" y="%.6f" z="%.6f" />\n' % (x, y, z))
|
||||||
idx_template = np.vectorize(lambda a, b, c: '<triangle v1="%d" v2="%d" v3="%d" />\n' % (a, b, c))
|
idx_template = np.vectorize(lambda a, b, c: '<triangle v1="%d" v2="%d" v3="%d" />\n' % (a, b, c))
|
||||||
|
|
@ -101,40 +111,41 @@ def write_model_xml(group: SlicingGroup, filename: str):
|
||||||
if uv.size and t_idx.size:
|
if uv.size and t_idx.size:
|
||||||
file.write(f' <object id="{str(i+1)}" type="model">\n')
|
file.write(f' <object id="{str(i+1)}" type="model">\n')
|
||||||
|
|
||||||
file.write(f' <mesh>\n')
|
file.write(' <mesh>\n')
|
||||||
|
|
||||||
file.write(f' <vertices>\n')
|
file.write(' <vertices>\n')
|
||||||
file.writelines(verts_template(uv[:,0], uv[:,1], uv[:,2]))
|
file.writelines(verts_template(uv[:,0], uv[:,1], uv[:,2]))
|
||||||
file.write(f' </vertices>\n')
|
file.write(' </vertices>\n')
|
||||||
|
|
||||||
file.write(f' <triangles>\n')
|
file.write(' <triangles>\n')
|
||||||
file.writelines(idx_template(t_idx[:,0], t_idx[:,1], t_idx[:,2]))
|
file.writelines(idx_template(t_idx[:,0], t_idx[:,1], t_idx[:,2]))
|
||||||
file.write(f' </triangles>\n')
|
file.write(' </triangles>\n')
|
||||||
|
|
||||||
file.write(f' </mesh>\n')
|
file.write(' </mesh>\n')
|
||||||
file.write(f' </object>\n')
|
file.write(' </object>\n')
|
||||||
|
|
||||||
file.write(f' </resources>\n')
|
file.write(' </resources>\n')
|
||||||
|
|
||||||
# Write build element
|
# Write build element
|
||||||
file.write(f' <build>\n')
|
file.write(' <build>\n')
|
||||||
ox, oy, oz = group.object_offset[0], group.object_offset[1], group.object_offset[2]
|
ox, oy, oz = getattr(group, "object_offset", (0.0, 0.0, 0.0))
|
||||||
transform_str = f"1 0 0 0 1 0 0 0 1 {ox} {oy} {oz}"
|
transform_str = f"1 0 0 0 1 0 0 0 1 {ox} {oy} {oz}"
|
||||||
for i, k in enumerate(valid_collections):
|
for i, k in enumerate(valid_collections):
|
||||||
if not valid_collections[k].unique_verts[0].size: continue
|
if not valid_collections[k].unique_verts[0].size: continue
|
||||||
file.writelines([f' <item objectid="{str(i+1)}" transform="{transform_str}" printable="1" />\n'])
|
file.writelines([f' <item objectid="{str(i+1)}" transform="{transform_str}" printable="1" />\n'])
|
||||||
file.write(f' </build>\n')
|
file.write(' </build>\n')
|
||||||
|
|
||||||
# Close the model tag
|
# Close the model tag
|
||||||
file.write(f'</model>\n')
|
file.write('</model>\n')
|
||||||
|
|
||||||
|
|
||||||
def to_3mf(folder_path, output_base_path):
|
def to_3mf(folder_path: str | Path, output_base_path: str | Path) -> None:
|
||||||
zip_file_path = shutil.make_archive(os.path.splitext(output_base_path)[0], 'zip', folder_path)
|
zip_file_path = shutil.make_archive(os.path.splitext(output_base_path)[0], 'zip', folder_path)
|
||||||
new_file_path = os.path.splitext(zip_file_path)[0] + '.3mf'
|
new_file_path = os.path.splitext(zip_file_path)[0] + '.3mf'
|
||||||
os.replace(zip_file_path, new_file_path)
|
os.replace(zip_file_path, new_file_path)
|
||||||
|
|
||||||
def write_z_gcodes(z_gcodes, filename: str):
|
|
||||||
|
def write_z_gcodes(z_gcodes, filename: str | Path) -> None:
|
||||||
root = ET.Element("custom_gcodes_per_print_z", bed_idx="0")
|
root = ET.Element("custom_gcodes_per_print_z", bed_idx="0")
|
||||||
|
|
||||||
for c in z_gcodes:
|
for c in z_gcodes:
|
||||||
|
|
@ -151,10 +162,10 @@ def write_z_gcodes(z_gcodes, filename: str):
|
||||||
|
|
||||||
ET.ElementTree(root).write(filename, encoding="utf-8", xml_declaration=True)
|
ET.ElementTree(root).write(filename, encoding="utf-8", xml_declaration=True)
|
||||||
|
|
||||||
def prepare_3mf(filepath: Path, geoms: SlicingGroup, conf, z_gcodes) -> None:
|
|
||||||
|
|
||||||
|
def prepare_3mf(filepath: Path, geoms: SlicingGroup, conf: Any, z_gcodes: Any, pg: Any = None, cx: Any = None) -> None:
|
||||||
source_folder = os.path.join(script_dir, 'prusaslicer_3mf')
|
source_folder = os.path.join(script_dir, 'prusaslicer_3mf')
|
||||||
temp_dir = tempfile.mkdtemp()
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
shutil.copytree(source_folder, temp_dir, dirs_exist_ok=True)
|
shutil.copytree(source_folder, temp_dir, dirs_exist_ok=True)
|
||||||
|
|
||||||
os.makedirs(os.path.join(temp_dir, '3D'), exist_ok=True)
|
os.makedirs(os.path.join(temp_dir, '3D'), exist_ok=True)
|
||||||
|
|
@ -168,5 +179,3 @@ def prepare_3mf(filepath: Path, geoms: SlicingGroup, conf, z_gcodes) -> None:
|
||||||
conf.write_ini_3mf(os.path.join(temp_dir, 'Metadata', 'Slic3r_PE.config'))
|
conf.write_ini_3mf(os.path.join(temp_dir, 'Metadata', 'Slic3r_PE.config'))
|
||||||
|
|
||||||
to_3mf(temp_dir, filepath)
|
to_3mf(temp_dir, filepath)
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
@ -170,7 +170,7 @@ class TestTransform(unittest.TestCase):
|
||||||
|
|
||||||
# Define mock resolver to return our inputs
|
# Define mock resolver to return our inputs
|
||||||
def mock_resolve(fieldname):
|
def mock_resolve(fieldname):
|
||||||
if fieldname == "Mesh":
|
if fieldname == "Geometry":
|
||||||
return sc
|
return sc
|
||||||
elif fieldname == "Translation":
|
elif fieldname == "Translation":
|
||||||
return (10.0, 20.0, 30.0)
|
return (10.0, 20.0, 30.0)
|
||||||
|
|
@ -184,7 +184,7 @@ class TestTransform(unittest.TestCase):
|
||||||
node.resolve = mock_resolve
|
node.resolve = mock_resolve
|
||||||
|
|
||||||
# Evaluate output
|
# Evaluate output
|
||||||
result_sc = node.evaluate_output("Mesh")
|
result_sc = node.evaluate_output("Geometry")
|
||||||
self.assertIsNotNone(result_sc)
|
self.assertIsNotNone(result_sc)
|
||||||
|
|
||||||
# Ensure it's not the same object (deep copied)
|
# Ensure it's not the same object (deep copied)
|
||||||
|
|
@ -338,13 +338,13 @@ class TestTransform(unittest.TestCase):
|
||||||
# 1. Transform Geometry
|
# 1. Transform Geometry
|
||||||
t_node = SLICENODE_TransformGeometry()
|
t_node = SLICENODE_TransformGeometry()
|
||||||
t_node.resolve = lambda f: {
|
t_node.resolve = lambda f: {
|
||||||
"Mesh": sc,
|
"Geometry": sc,
|
||||||
"Translation": (10.0, 20.0, 0.0),
|
"Translation": (10.0, 20.0, 0.0),
|
||||||
"Rotation": (0.0, 0.0, 0.0),
|
"Rotation": (0.0, 0.0, 0.0),
|
||||||
"Scale": (1.0, 1.0, 1.0)
|
"Scale": (1.0, 1.0, 1.0)
|
||||||
}.get(f)
|
}.get(f)
|
||||||
|
|
||||||
sc_transformed = t_node.evaluate_output("Mesh")
|
sc_transformed = t_node.evaluate_output("Geometry")
|
||||||
self.assertIsNotNone(sc_transformed)
|
self.assertIsNotNone(sc_transformed)
|
||||||
self.assertEqual(len(sc_transformed.transforms), 1)
|
self.assertEqual(len(sc_transformed.transforms), 1)
|
||||||
self.assertEqual(sc_transformed.transforms[0]["type"], "transform_geometry")
|
self.assertEqual(sc_transformed.transforms[0]["type"], "transform_geometry")
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,33 @@ import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||||
sys.path.append(str(PROJECT_ROOT))
|
sys.path.append(str(PROJECT_ROOT.parent))
|
||||||
|
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
class DummyBase: pass
|
||||||
|
mock_types = MagicMock()
|
||||||
|
mock_types.NodeSocket = type("NodeSocket", (DummyBase,), {})
|
||||||
|
mock_types.Node = type("Node", (DummyBase,), {})
|
||||||
|
mock_types.NodeCustomGroup = type("NodeCustomGroup", (DummyBase,), {})
|
||||||
|
mock_types.NodeTree = type("NodeTree", (DummyBase,), {})
|
||||||
|
mock_types.PropertyGroup = type("PropertyGroup", (DummyBase,), {})
|
||||||
|
mock_types.Operator = type("Operator", (DummyBase,), {})
|
||||||
|
mock_types.Panel = type("Panel", (DummyBase,), {})
|
||||||
|
mock_types.Object = MagicMock
|
||||||
|
mock_types.Mesh = MagicMock
|
||||||
|
|
||||||
|
mock_bpy = MagicMock()
|
||||||
|
mock_bpy.types = mock_types
|
||||||
|
mock_bpy.path.abspath = lambda path: path.replace("//", "/abs/")
|
||||||
|
|
||||||
|
sys.modules['bpy'] = mock_bpy
|
||||||
|
sys.modules['bpy.types'] = mock_types
|
||||||
|
sys.modules['bpy.props'] = MagicMock()
|
||||||
|
sys.modules['bpy.utils'] = MagicMock()
|
||||||
|
sys.modules['bpy.utils.previews'] = MagicMock()
|
||||||
|
sys.modules['bpy.app'] = MagicMock()
|
||||||
|
sys.modules['bpy.app.handlers'] = MagicMock()
|
||||||
|
|
||||||
from NodeSlicer.core.nodeslicer.nodes.inputs import SLICENODE_WipeTower, SLICENODE_OffsetObject
|
from NodeSlicer.core.nodeslicer.nodes.inputs import SLICENODE_WipeTower, SLICENODE_OffsetObject
|
||||||
from NodeSlicer.infra.prusaslicer_profiles.models import ConfigWriter
|
from NodeSlicer.infra.prusaslicer_profiles.models import ConfigWriter
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue