fix step save flow and add portable backend runtime support
Some checks failed
Build / build (18.x, macos-latest) (push) Has been cancelled
Build / build (18.x, ubuntu-latest) (push) Has been cancelled
Build / build (18.x, windows-latest) (push) Has been cancelled
Build / build (20.x, macos-latest) (push) Has been cancelled
Build / build (20.x, ubuntu-latest) (push) Has been cancelled
Build / build (20.x, windows-latest) (push) Has been cancelled
Some checks failed
Build / build (18.x, macos-latest) (push) Has been cancelled
Build / build (18.x, ubuntu-latest) (push) Has been cancelled
Build / build (18.x, windows-latest) (push) Has been cancelled
Build / build (20.x, macos-latest) (push) Has been cancelled
Build / build (20.x, ubuntu-latest) (push) Has been cancelled
Build / build (20.x, windows-latest) (push) Has been cancelled
This commit is contained in:
parent
b447fc7864
commit
16a2e43649
4
.gitignore
vendored
4
.gitignore
vendored
@ -3,3 +3,7 @@ plugins/*
|
||||
node_modules
|
||||
__pycache__
|
||||
.worktrees
|
||||
.codex/
|
||||
runtime/python/
|
||||
runtime/freecad/
|
||||
runtime/FreeCAD*/
|
||||
|
||||
@ -153,10 +153,6 @@ export class Navigator
|
||||
|
||||
FillTree (importResult)
|
||||
{
|
||||
console.log ('[step-demo] FillTree start', {
|
||||
usedFiles : importResult.usedFiles ? importResult.usedFiles.length : null,
|
||||
missingFiles : importResult.missingFiles ? importResult.missingFiles.length : null
|
||||
});
|
||||
this.filesPanel.Clear ();
|
||||
this.materialsPanel.Clear ();
|
||||
this.meshesPanel.Clear ();
|
||||
@ -168,10 +164,6 @@ export class Navigator
|
||||
}
|
||||
this.materialsPanel.Fill (importResult);
|
||||
this.meshesPanel.Fill (importResult);
|
||||
console.log ('[step-demo] FillTree done', {
|
||||
meshItems : this.meshesPanel.MeshItemCount (),
|
||||
rootChildren : this.meshesPanel.rootItem !== null ? this.meshesPanel.rootItem.children.length : null
|
||||
});
|
||||
this.OnSelectionChanged ();
|
||||
}
|
||||
|
||||
|
||||
@ -111,10 +111,6 @@ export class NavigatorMeshesPanel extends NavigatorPanel
|
||||
|
||||
Clear ()
|
||||
{
|
||||
console.log ('[step-demo] MeshesPanel.Clear', {
|
||||
nodeItems : this.nodeIdToItem.size,
|
||||
meshItems : this.meshInstanceIdToItem.size
|
||||
});
|
||||
this.ClearMeshTree ();
|
||||
ClearDomElement (this.titleButtonsDiv);
|
||||
ClearDomElement (this.buttonsDiv);
|
||||
@ -435,10 +431,6 @@ export class NavigatorMeshesPanel extends NavigatorPanel
|
||||
let rootNode = model.GetRootNode ();
|
||||
this.rootItem = CreateDummyRootItem (this, rootNode);
|
||||
AddModelNodeToTree (this, model, rootNode, this.rootItem, this.mode);
|
||||
console.log ('[step-demo] FillMeshTree', {
|
||||
rootChildren : this.rootItem.children.length,
|
||||
labels : this.rootItem.children.map ((child) => child.name)
|
||||
});
|
||||
}
|
||||
|
||||
UpdateMaterialList (materialInfoArray)
|
||||
@ -483,9 +475,6 @@ export class NavigatorMeshesPanel extends NavigatorPanel
|
||||
{
|
||||
let meshItem = this.GetMeshItem (meshInstanceId);
|
||||
if (meshItem === undefined) {
|
||||
console.log ('[step-demo] IsMeshVisible missing mesh item', {
|
||||
meshKey : meshInstanceId.GetKey ()
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return meshItem.IsVisible ();
|
||||
|
||||
@ -33,7 +33,7 @@ export class StepDeletionState
|
||||
this.meshKeyToNodePath.set (meshInstanceId.GetKey (), nodePath);
|
||||
}
|
||||
|
||||
let childNodes = node.GetChildNodes ();
|
||||
let childNodes = this.GetChildNodesInPathOrder (node);
|
||||
for (let childIndex = 0; childIndex < childNodes.length; childIndex++) {
|
||||
let childNode = childNodes[childIndex];
|
||||
let childPath = nodePath.length === 0 ? childIndex.toString () : nodePath + '/' + childIndex.toString ();
|
||||
@ -41,6 +41,20 @@ export class StepDeletionState
|
||||
}
|
||||
}
|
||||
|
||||
GetChildNodesInPathOrder (node)
|
||||
{
|
||||
let assemblyNodes = [];
|
||||
let leafPartNodes = [];
|
||||
for (let childNode of node.GetChildNodes ()) {
|
||||
if (childNode.IsMeshNode ()) {
|
||||
leafPartNodes.push (childNode);
|
||||
} else {
|
||||
assemblyNodes.push (childNode);
|
||||
}
|
||||
}
|
||||
return assemblyNodes.concat (leafPartNodes);
|
||||
}
|
||||
|
||||
GetNodePath (nodeId)
|
||||
{
|
||||
return this.nodeIdToPath.get (nodeId);
|
||||
|
||||
@ -539,10 +539,6 @@ export class Website
|
||||
deletedPath = this.stepDeletionState.GetMeshNodePath (this.navigator.selection.meshInstanceId);
|
||||
this.stepDeletionState.DeleteMeshNode (this.navigator.selection.meshInstanceId);
|
||||
}
|
||||
console.log ('[step-demo] DeleteSelectedStepNode', {
|
||||
deletedPath : deletedPath,
|
||||
deletedPaths : this.stepDeletionState.GetDeletedNodePaths ()
|
||||
});
|
||||
|
||||
this.navigator.SetSelection (null);
|
||||
this.navigator.FillTree ({
|
||||
@ -566,7 +562,6 @@ export class Website
|
||||
|
||||
async SaveEditedStepFile ()
|
||||
{
|
||||
console.log ('[step-demo] SaveEditedStepFile start');
|
||||
let importer = this.modelLoaderUI.GetImporter ();
|
||||
let fileList = importer.GetFileList ();
|
||||
let mainFileName = this.currentImportResult !== null ? this.currentImportResult.mainFile : this.parameters.fileNameDiv.textContent;
|
||||
@ -575,13 +570,6 @@ export class Website
|
||||
sourceFile = fileList.GetFiles ()[0];
|
||||
}
|
||||
|
||||
console.log ('[step-demo] SaveEditedStepFile source', {
|
||||
mainFileName : mainFileName,
|
||||
foundSource : sourceFile !== null,
|
||||
hasContent : sourceFile !== null ? sourceFile.content !== null : null,
|
||||
deletedPaths : this.stepDeletionState !== null ? this.stepDeletionState.GetDeletedNodePaths () : null
|
||||
});
|
||||
|
||||
if (this.stepDeletionState === null) {
|
||||
ShowMessageDialog (
|
||||
Loc ('Save STEP Failed'),
|
||||
@ -620,10 +608,6 @@ export class Website
|
||||
sourceFile.content,
|
||||
this.stepDeletionState.GetDeletedNodePaths ()
|
||||
);
|
||||
console.log ('[step-demo] SaveEditedStepFile response', {
|
||||
size : fileBlob.size,
|
||||
type : fileBlob.type
|
||||
});
|
||||
let downloadUrl = URL.createObjectURL (fileBlob);
|
||||
DownloadUrlAsFile (downloadUrl, BuildStepOutputFileName (sourceFile.name));
|
||||
window.setTimeout (() => {
|
||||
|
||||
@ -52,6 +52,34 @@ describe ('StepDeletionState', function () {
|
||||
assert.strictEqual (state.CanDeleteNode (1), true);
|
||||
assert.strictEqual (state.CanDeleteNode (3), true);
|
||||
});
|
||||
|
||||
it ('orders assembly nodes before leaf parts when building deletion paths', function () {
|
||||
let model = new OV.Model ();
|
||||
let root = model.GetRootNode ();
|
||||
|
||||
let leafPartMeshIndex = model.AddMesh (new OV.Mesh ());
|
||||
let leafPart = new OV.Node ();
|
||||
leafPart.SetName ('Leaf Part');
|
||||
root.AddChildNode (leafPart);
|
||||
leafPart.AddMeshIndex (leafPartMeshIndex);
|
||||
|
||||
let assembly = new OV.Node ();
|
||||
assembly.SetName ('Assembly');
|
||||
root.AddChildNode (assembly);
|
||||
|
||||
let assemblyLeafPartMeshIndex = model.AddMesh (new OV.Mesh ());
|
||||
let assemblyLeafPart = new OV.Node ();
|
||||
assemblyLeafPart.SetName ('Assembly Leaf Part');
|
||||
assembly.AddChildNode (assemblyLeafPart);
|
||||
assemblyLeafPart.AddMeshIndex (assemblyLeafPartMeshIndex);
|
||||
|
||||
let state = new StepDeletionState (model);
|
||||
|
||||
assert.strictEqual (state.GetNodePath (assembly.GetId ()), '0');
|
||||
assert.strictEqual (state.GetNodePath (assemblyLeafPart.GetId ()), '0/0');
|
||||
assert.strictEqual (state.GetNodePath (leafPart.GetId ()), '1');
|
||||
assert.strictEqual (state.GetMeshNodePath (new OV.MeshInstanceId (leafPart.GetId (), leafPartMeshIndex)), '1');
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
56
tools/step_service/PORTABLE_RUNTIME.md
Normal file
56
tools/step_service/PORTABLE_RUNTIME.md
Normal file
@ -0,0 +1,56 @@
|
||||
# Portable STEP Service Runtime
|
||||
|
||||
## Goal
|
||||
|
||||
Run the STEP save backend from this project folder on another Windows machine without installing system Python or system FreeCAD.
|
||||
|
||||
## Expected layout
|
||||
|
||||
Put these runtimes inside the project root:
|
||||
|
||||
```text
|
||||
Online3DViewer/
|
||||
runtime/
|
||||
python/
|
||||
python.exe
|
||||
...
|
||||
freecad/
|
||||
bin/
|
||||
FreeCADCmd.exe
|
||||
...
|
||||
```
|
||||
|
||||
## One-time setup on the source machine
|
||||
|
||||
1. Copy a full Windows Python runtime into `runtime/python`.
|
||||
2. Copy the FreeCAD folder into `runtime/freecad`.
|
||||
3. Install backend dependencies into the portable Python:
|
||||
|
||||
```bat
|
||||
tools\step_service\setup_portable_python.bat
|
||||
```
|
||||
|
||||
## Start the backend
|
||||
|
||||
Use the portable launcher:
|
||||
|
||||
```bat
|
||||
tools\step_service\start_portable_server.bat
|
||||
```
|
||||
|
||||
This launcher only uses `runtime/python/python.exe`. It does not modify system PATH.
|
||||
|
||||
## How FreeCAD is resolved
|
||||
|
||||
The backend now checks FreeCAD in this order:
|
||||
|
||||
1. `runtime/freecad/bin/FreeCADCmd.exe`
|
||||
2. `FreeCADCmd` from the current environment
|
||||
|
||||
So your existing local setup still works, but the portable folder takes priority when present.
|
||||
|
||||
## Notes
|
||||
|
||||
- Copy the whole project folder to the target machine.
|
||||
- The target machine still needs normal Windows runtime support that FreeCAD and Python depend on.
|
||||
- If you want zero manual commands on the target machine, use `start_portable_server.bat` instead of `python tools/step_service/server.py`.
|
||||
@ -41,6 +41,15 @@ def enumerate_objects(objects, parent_path=""):
|
||||
yield from enumerate_objects(child_objects, object_path)
|
||||
|
||||
|
||||
def collect_all_subtree_objects(objects):
|
||||
collected_objects = []
|
||||
for document_object in objects:
|
||||
collected_objects.append(document_object)
|
||||
child_objects = list(getattr(document_object, "OutList", []))
|
||||
collected_objects.extend(collect_all_subtree_objects(child_objects))
|
||||
return collected_objects
|
||||
|
||||
|
||||
def collect_subtree_objects(objects, target_path, parent_path=""):
|
||||
collected_objects = []
|
||||
filtered_objects = get_tree_children(objects)
|
||||
@ -49,15 +58,30 @@ def collect_subtree_objects(objects, target_path, parent_path=""):
|
||||
child_objects = list(getattr(document_object, "OutList", []))
|
||||
if target_path is None:
|
||||
collected_objects.append(document_object)
|
||||
collected_objects.extend(collect_subtree_objects(child_objects, None, object_path))
|
||||
collected_objects.extend(collect_all_subtree_objects(child_objects))
|
||||
elif object_path == target_path:
|
||||
collected_objects.append(document_object)
|
||||
collected_objects.extend(collect_subtree_objects(child_objects, None, object_path))
|
||||
collected_objects.extend(collect_all_subtree_objects(child_objects))
|
||||
elif target_path.startswith(object_path + "/"):
|
||||
collected_objects.extend(collect_subtree_objects(child_objects, target_path, object_path))
|
||||
return collected_objects
|
||||
|
||||
|
||||
def build_removal_names(objects_to_delete):
|
||||
removal_candidates = []
|
||||
seen_names = set()
|
||||
for document_object in objects_to_delete:
|
||||
object_name = getattr(document_object, "Name", None)
|
||||
if object_name is None or object_name in seen_names:
|
||||
continue
|
||||
seen_names.add(object_name)
|
||||
subtree_depth = len(list(getattr(document_object, "OutListRecursive", [])))
|
||||
removal_candidates.append((object_name, subtree_depth))
|
||||
|
||||
removal_candidates.sort(key=lambda candidate: candidate[1], reverse=True)
|
||||
return [object_name for object_name, _depth in removal_candidates]
|
||||
|
||||
|
||||
def main():
|
||||
input_path, deleted_paths_path, output_path = get_script_arguments(sys.argv)
|
||||
|
||||
@ -75,18 +99,10 @@ def main():
|
||||
for deleted_path in deleted_paths:
|
||||
objects_to_delete.extend(collect_subtree_objects(root_objects, deleted_path))
|
||||
|
||||
unique_objects = []
|
||||
seen_names = set()
|
||||
for document_object in objects_to_delete:
|
||||
object_name = getattr(document_object, "Name", None)
|
||||
if object_name is None or object_name in seen_names:
|
||||
for object_name in build_removal_names(objects_to_delete):
|
||||
if document.getObject(object_name) is None:
|
||||
continue
|
||||
seen_names.add(object_name)
|
||||
unique_objects.append(document_object)
|
||||
|
||||
unique_objects.sort(key=lambda document_object: len(list(getattr(document_object, "OutListRecursive", []))), reverse=True)
|
||||
for document_object in unique_objects:
|
||||
document.removeObject(document_object.Name)
|
||||
document.removeObject(object_name)
|
||||
|
||||
Import.export(list(document.RootObjects), output_path)
|
||||
FreeCAD.closeDocument(document.Name)
|
||||
|
||||
@ -8,8 +8,41 @@ from .step_tree import normalize_deleted_paths
|
||||
|
||||
|
||||
class FreeCADWorker:
|
||||
def __init__(self, freecad_cmd="FreeCADCmd"):
|
||||
def __init__(self, freecad_cmd="FreeCADCmd", project_root=None):
|
||||
self.freecad_cmd = freecad_cmd
|
||||
if project_root is None:
|
||||
self.project_root = pathlib.Path(__file__).resolve().parents[2]
|
||||
else:
|
||||
self.project_root = pathlib.Path(project_root)
|
||||
|
||||
def get_freecad_cmd(self):
|
||||
if isinstance(self.freecad_cmd, str) and self.freecad_cmd == "FreeCADCmd":
|
||||
project_freecad_cmd = self._get_project_freecad_cmd()
|
||||
if project_freecad_cmd is not None:
|
||||
return project_freecad_cmd
|
||||
return self.freecad_cmd
|
||||
|
||||
def _get_project_freecad_cmd(self):
|
||||
runtime_dir = self.project_root / "runtime"
|
||||
candidate_commands = [
|
||||
runtime_dir / "freecad" / "bin" / "FreeCADCmd.exe",
|
||||
runtime_dir / "FreeCAD" / "bin" / "FreeCADCmd.exe",
|
||||
]
|
||||
for child in runtime_dir.iterdir() if runtime_dir.exists() else []:
|
||||
child_name = child.name.lower()
|
||||
if "freecad" not in child_name:
|
||||
continue
|
||||
candidate_commands.extend(
|
||||
[
|
||||
child / "bin" / "FreeCADCmd.exe",
|
||||
child / "bin" / "freecadcmd.exe",
|
||||
]
|
||||
)
|
||||
|
||||
for project_freecad_cmd in candidate_commands:
|
||||
if project_freecad_cmd.exists():
|
||||
return project_freecad_cmd
|
||||
return None
|
||||
|
||||
def trim_step_file(self, file_name, file_bytes, deleted_paths):
|
||||
normalized_paths = normalize_deleted_paths(deleted_paths)
|
||||
@ -28,7 +61,7 @@ class FreeCADWorker:
|
||||
|
||||
subprocess.run(
|
||||
[
|
||||
self.freecad_cmd,
|
||||
os.fspath(self.get_freecad_cmd()),
|
||||
os.fspath(pathlib.Path(__file__).with_name("freecad_trim_step.py")),
|
||||
"--pass",
|
||||
os.fspath(input_path),
|
||||
|
||||
16
tools/step_service/setup_portable_python.bat
Normal file
16
tools/step_service/setup_portable_python.bat
Normal file
@ -0,0 +1,16 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
set "SCRIPT_DIR=%~dp0"
|
||||
set "PROJECT_ROOT=%SCRIPT_DIR%..\.."
|
||||
set "PYTHON_EXE=%PROJECT_ROOT%\runtime\python\python.exe"
|
||||
set "REQUIREMENTS_FILE=%SCRIPT_DIR%requirements.txt"
|
||||
set "PYTHONNOUSERSITE=1"
|
||||
|
||||
if not exist "%PYTHON_EXE%" (
|
||||
echo [step_service] Missing portable Python: "%PYTHON_EXE%"
|
||||
echo [step_service] Copy a full Python runtime into "runtime\python" first.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
"%PYTHON_EXE%" -m pip install --isolated --no-warn-script-location -r "%REQUIREMENTS_FILE%"
|
||||
15
tools/step_service/start_portable_server.bat
Normal file
15
tools/step_service/start_portable_server.bat
Normal file
@ -0,0 +1,15 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
set "SCRIPT_DIR=%~dp0"
|
||||
set "PROJECT_ROOT=%SCRIPT_DIR%..\.."
|
||||
set "PYTHON_EXE=%PROJECT_ROOT%\runtime\python\python.exe"
|
||||
set "PYTHONNOUSERSITE=1"
|
||||
|
||||
if not exist "%PYTHON_EXE%" (
|
||||
echo [step_service] Missing portable Python: "%PYTHON_EXE%"
|
||||
echo [step_service] Copy a full Python runtime into "runtime\python" first.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
"%PYTHON_EXE%" "%SCRIPT_DIR%server.py"
|
||||
@ -1,6 +1,6 @@
|
||||
import unittest
|
||||
|
||||
from tools.step_service.freecad_trim_step import collect_subtree_objects, get_script_arguments, get_tree_children, should_include_in_tree
|
||||
from tools.step_service.freecad_trim_step import build_removal_names, collect_subtree_objects, get_script_arguments, get_tree_children, should_include_in_tree
|
||||
|
||||
|
||||
class FreeCADTrimStepTests(unittest.TestCase):
|
||||
@ -73,6 +73,46 @@ class FreeCADTrimStepTests(unittest.TestCase):
|
||||
["L_BRACKET_ASSEMBLY_ASM", "NUT_BOLT_ASSEMBLY_ASM", "BOLT", "NUT"],
|
||||
)
|
||||
|
||||
def test_collects_hidden_shape_children_when_deleting_visible_part(self):
|
||||
class FakeObject:
|
||||
def __init__(self, label, type_id, children=None):
|
||||
self.Label = label
|
||||
self.TypeId = type_id
|
||||
self.OutList = children or []
|
||||
|
||||
shape = FakeObject("ROD_SHAPE", "Part::Feature")
|
||||
rod = FakeObject("ROD", "App::Part", [shape])
|
||||
support_shape = FakeObject("SUPPORT_SHAPE", "Part::Feature")
|
||||
support = FakeObject("SUPPORT", "App::Part", [support_shape])
|
||||
|
||||
collected = collect_subtree_objects([rod, support], "0")
|
||||
|
||||
self.assertEqual(
|
||||
[obj.Label for obj in collected],
|
||||
["ROD", "ROD_SHAPE"],
|
||||
)
|
||||
|
||||
def test_builds_removal_names_before_objects_become_invalid(self):
|
||||
class FakeObject:
|
||||
def __init__(self, name, depth):
|
||||
self.Name = name
|
||||
self._depth = depth
|
||||
|
||||
@property
|
||||
def OutListRecursive(self):
|
||||
return [object()] * self._depth
|
||||
|
||||
removal_names = build_removal_names(
|
||||
[
|
||||
FakeObject("ROD_ASM", 3),
|
||||
FakeObject("ROD", 1),
|
||||
FakeObject("ROD_SHAPE", 0),
|
||||
FakeObject("ROD", 1),
|
||||
]
|
||||
)
|
||||
|
||||
self.assertEqual(removal_names, ["ROD_ASM", "ROD", "ROD_SHAPE"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import pathlib
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
@ -7,6 +8,36 @@ from tools.step_service.freecad_worker import FreeCADWorker
|
||||
|
||||
|
||||
class FreeCADWorkerTests(unittest.TestCase):
|
||||
def test_prefers_project_local_freecad_command(self):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
project_root = temp_dir
|
||||
freecad_cmd = mock.Mock()
|
||||
freecad_cmd.exists.return_value = True
|
||||
freecad_cmd.__fspath__ = mock.Mock(return_value="D:/portable/freecad/bin/FreeCADCmd.exe")
|
||||
|
||||
worker = FreeCADWorker(project_root=project_root)
|
||||
|
||||
with mock.patch.object(worker, "_get_project_freecad_cmd", return_value=freecad_cmd):
|
||||
self.assertEqual(worker.get_freecad_cmd(), freecad_cmd)
|
||||
|
||||
def test_falls_back_to_system_command_when_project_runtime_is_missing(self):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
worker = FreeCADWorker(project_root=temp_dir)
|
||||
|
||||
with mock.patch.object(worker, "_get_project_freecad_cmd", return_value=None):
|
||||
self.assertEqual(worker.get_freecad_cmd(), "FreeCADCmd")
|
||||
|
||||
def test_detects_freecad_runtime_with_versioned_folder_name(self):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
project_root_path = pathlib.Path(temp_dir)
|
||||
command_path = project_root_path / "runtime" / "FreeCAD 1.0" / "bin" / "freecadcmd.exe"
|
||||
command_path.parent.mkdir(parents=True)
|
||||
command_path.write_text("", encoding="utf-8")
|
||||
|
||||
worker = FreeCADWorker(project_root=project_root_path)
|
||||
|
||||
self.assertEqual(worker.get_freecad_cmd(), command_path)
|
||||
|
||||
@mock.patch("tools.step_service.freecad_worker.subprocess.run")
|
||||
def test_passes_script_arguments_through_freecad(self, run_mock):
|
||||
run_mock.return_value = subprocess.CompletedProcess(args=[], returncode=0, stdout="", stderr="")
|
||||
@ -17,14 +48,16 @@ class FreeCADWorkerTests(unittest.TestCase):
|
||||
with mock.patch("tools.step_service.freecad_worker.tempfile.TemporaryDirectory") as temp_dir_mock:
|
||||
temp_dir_mock.return_value.__enter__.return_value = temp_dir
|
||||
temp_dir_mock.return_value.__exit__.return_value = False
|
||||
with mock.patch("pathlib.Path.read_bytes", return_value=b"STEPDATA"):
|
||||
with mock.patch("pathlib.Path.exists", return_value=True):
|
||||
output_bytes, output_name = worker.trim_step_file("demo.step", b"INPUT", ["0/1"])
|
||||
with mock.patch.object(worker, "get_freecad_cmd", return_value="FreeCADCmd"):
|
||||
with mock.patch("pathlib.Path.read_bytes", return_value=b"STEPDATA"):
|
||||
with mock.patch("pathlib.Path.exists", return_value=True):
|
||||
output_bytes, output_name = worker.trim_step_file("demo.step", b"INPUT", ["0/1"])
|
||||
|
||||
self.assertEqual(output_bytes, b"STEPDATA")
|
||||
self.assertEqual(output_name, "demo-edited.step")
|
||||
command = run_mock.call_args.args[0]
|
||||
self.assertIn("--pass", command)
|
||||
self.assertEqual(command[0], "FreeCADCmd")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Loading…
Reference in New Issue
Block a user