three.scanner

  1# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  2# This file is Auto Generated by generateScannerPy.py. Do not edit this file manually.
  3# Modifications should be made to _scanner.py and then run generateScannerPy.py to update this file.
  4# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  5
  6## @package three
  7# @file scanner.py
  8# @brief Scanner class to wrap websocket connection. This file will be copied and amended with the three methods.
  9# @date 2024-11-27
 10# @copyright © 2024 Matter and Form. All rights reserved.
 11
 12from MF.V3 import Three
 13from MF.V3.Task import Task
 14from MF.V3.Settings.Align import Align
 15from MF.V3.Settings.AutoFocus import AutoFocus
 16from MF.V3.Settings.ScanSelection import ScanSelection
 17from MF.V3.Settings.CaptureImage import CaptureImage
 18from MF.V3.Settings.Camera import Camera
 19from MF.V3.Settings.Projector import Projector
 20from MF.V3.Settings.Turntable import Turntable
 21from MF.V3.Settings.Capture import Capture
 22from MF.V3.Settings.Scan import Scan
 23from MF.V3.Settings.Export import Export
 24from MF.V3.Settings.Import import Import
 25from MF.V3.Settings.Merge import Merge
 26from MF.V3.Settings.ScanData import ScanData
 27from MF.V3.Settings.Advanced import Advanced
 28from MF.V3.Settings.I18n import I18n
 29from MF.V3.Settings.Style import Style
 30from MF.V3.Settings.Tutorials import Tutorials
 31from MF.V3.Settings.Viewer import Viewer
 32from MF.V3.Settings.Software import Software
 33
 34from typing import Any, Callable, Optional, List
 35import websocket
 36import json
 37import threading
 38import time
 39from MF.V3 import Task, TaskState, Buffer
 40from three import __version__
 41import three.MF
 42from three.serialization import TO_JSON
 43from three.MF.V3.Buffer import Buffer
 44
 45
 46
 47
 48class Scanner:
 49    """
 50    Main class to manage and communicate with the Matter and Form THREE 3D Scanner via websocket.
 51
 52    Attributes:
 53        * OnTask (Optional[Callable[[Task], None]]): Callback for any task related messages, default is None. Will fire for task complete, progress, and error.
 54        * OnMessage (Optional[Callable[[str], None]]): Callback function to handle messages, default is None. Messages are any calls related to the system that are not tasks or buffers. Eg. {"Message":{"HasTurntable":true}}
 55        * OnBuffer (Optional[Callable[[Any, bytes], None]]): Callback Function to handle buffer data, default is None. Buffers can be image or scan data. These are binary formats that are sent separately from tasks. Buffers will contain a header that describes the buffer data size. Please note that websocket buffers are limited in size and need to be sent in chunks.
 56    """
 57    
 58    __bufferDescriptor = None
 59    __buffer = None
 60    __error = None
 61    __taskIndex:int = 0
 62    __tasks:List[Task] = []
 63
 64
 65    def __init__(self,
 66        OnTask: Optional[Callable[[Task], None]] = None,
 67        OnMessage: Optional[Callable[[str], None]] = None,
 68        OnBuffer: Optional[Callable[[Any, bytes], None]] = None,
 69        ):
 70        """
 71        Initializes the Scanner object.
 72
 73        Args:
 74            * OnTask (Optional[Callable[[Task], None]]): Callback for any task related messages, default is None. Will fire for task complete, progress, and error.
 75            * OnMessage (Optional[Callable[[str], None]]): Callback function to handle messages, default is None. Messages are any calls related to the system that are not tasks or buffers. Eg. {"Message":{"HasTurntable":true}}
 76            * OnBuffer (Optional[Callable[[Any, bytes], None]]): Callback Function to handle buffer data, default is None. Buffers can be image or scan data. These are binary formats that are sent separately from tasks. Buffers will contain a header that describes the buffer data size. Please note that websocket buffers are limited in size and need to be sent in chunks.
 77        """
 78        self.__isConnected = False
 79
 80        self.OnTask = OnTask
 81        self.OnMessage = OnMessage
 82        self.OnBuffer = OnBuffer
 83        
 84        self.__task_return_event = threading.Event()
 85        
 86        # Dynamically add methods from Three to Scanner
 87        # self._add_three_methods()
 88
 89    # def _add_three_methods(self):
 90    #     """
 91    #     Dynamically adds functions from the three_methods module to the Scanner class.
 92    #     """
 93    #     for name, func in inspect.getmembers(Three, predicate=inspect.isfunction):
 94    #         if not name.startswith('_'):
 95    #             setattr(self, name, func.__get__(self, self.__class__))
 96
 97
 98    def Connect(self, URI:str, timeoutSec=5) -> bool:
 99        """
100        Attempts to connect to the scanner using the specified URI and timeout.
101
102        Args:
103            * URI (str): The URI of the websocket server.
104            * timeoutSec (int): Timeout in seconds, default is 5.
105
106        Returns:
107            bool: True if connection is successful, raises Exception otherwise.
108
109        Raises:
110            Exception: If connection fails within the timeout or due to an error.
111        """
112        print('Connecting to: ', URI)
113        self.__URI = URI
114        self.__isConnected = False
115        self.__error = None
116
117        self.__serverVersion__= None
118
119        self.websocket = websocket.WebSocketApp(self.__URI,
120                              on_open=self.__OnOpen,
121                              on_close=self.__OnClose,
122                              on_error=self.__OnError,
123                              on_message=self.__OnMessage,
124                              )
125        
126        wst = threading.Thread(target=self.websocket.run_forever)
127        wst.daemon = True
128        wst.start()
129
130        # Wait for connection
131        start = time.time()
132        while time.time() < start + timeoutSec:
133            if self.__isConnected:
134                # Not checking versions => return True
135                    return True
136            elif self.__error:
137                raise Exception(self.__error)
138            time.sleep(0.1)
139        
140        raise Exception('Connection timeout')
141        
142    def Disconnect(self) -> None:
143        """
144        Close the websocket connection.
145        """
146        if self.__isConnected:
147            # Close the connection
148            self.websocket.close()
149            # Wait for the connection to be closed.
150            while self.__isConnected:
151                time.sleep(0.1)
152
153    def IsConnected(self)-> bool:
154        """
155        Checks if the scanner is currently connected.
156
157        Returns:
158            bool: True if connected, False otherwise.
159        """
160        return self.__isConnected
161    
162    def __callback(self, callback, *args) -> None:
163        if callback:
164                callback(self, *args)
165
166    # Called when the connection is opened
167    def __OnOpen(self, ws) -> None:
168        """
169        Callback function for when the websocket connection is successfully opened.
170
171        Prints a success message to the console.
172
173        Args:
174            ws: The websocket object.
175        """
176        self.__isConnected = True
177        print('Connected to: ', self.__URI)
178
179    # Called when the connection is closed
180    def __OnClose(self, ws, close_status_code, close_msg):
181        """
182        Callback function for when the websocket connection is closed.
183
184        Prints a disconnect message to the console.
185
186        Args:
187            ws: The websocket object.
188            close_status_code: The code indicating why the websocket was closed.
189            close_msg: Additional message about why the websocket was closed.
190        """
191        if self.__isConnected:
192            print('Disconnected')
193        self.__isConnected = False
194
195    # Called when an error happens
196    def __OnError(self, ws, error) -> None:
197        """
198        Callback function for when an error occurs in the websocket connection.
199
200        Prints an error message to the console and stores the error for reference.
201
202        Args:
203            ws: The websocket object.
204            error: The error that occurred.
205        """
206        if self.__isConnected:
207            print('Error: ', error)    
208        else:
209            self.__error = error
210        
211    # Called when a message arrives on the connection
212    def __OnMessage(self, ws, message) -> None:
213        """
214        Callback function for handling messages received via the websocket.
215
216        Determines the type of message received (Task, Buffer, or general Message) and
217        triggers the corresponding handler function if one is set.
218
219        Args:
220            ws: The websocket object.
221            message: The raw message received, which can be either a byte string or a JSON string.
222        """
223        # Bytes ?
224        if isinstance(message, bytes):
225            if self.OnBuffer:
226                
227                if self.__buffer:
228                    self.__buffer += message
229                else:
230                    self.__buffer = message
231                if self.__bufferDescriptor.Size == len(self.__buffer):
232                    self.OnBuffer(self.__bufferDescriptor, message)
233                    self.__bufferDescriptor = None 
234                    self.__buffer = None
235        else:
236            obj = json.loads(message)              
237        
238            # Task
239            if 'Task' in obj:
240                # Create the task from the message
241                task = Task(**obj['Task'])
242                
243                if (task.Progress):
244                    # Extract the first (and only) item from the task.Progress dictionary
245                    # TODO Duct tape fix due to schema weirdness for progress
246                    key, process = next(iter(task.Progress.items()))
247                    task.Progress = type('Progress', (object,), {
248                        'current': process["current"],
249                        'step': process["step"],
250                        'total': process["total"]
251                    })()
252
253                # Find the original task for reference
254                inputTask = self.__FindTaskWithIndex(task.Index)
255                if inputTask == None:
256                    raise Exception('Task not found')
257                    
258                if task.Error:
259                    inputTask.Error = task.Error
260                    self.__OnError(self.websocket, task.Error)
261                    self.__task_return_event.set()
262                    
263                # If assigned => Call the handler
264                if self.OnTask:
265                    self.OnTask(task)
266                
267                
268                # If waiting for a response, set the response and notify
269                if (task.State == TaskState.Completed.value):
270                    if task.Output:
271                        inputTask.Output = task.Output
272                    self.__task_return_event.set()
273                elif (task.State == TaskState.Failed.value):
274                    inputTask.Error = task.Error
275                    self.__task_return_event
276                    
277            # Buffer
278            elif 'Buffer' in obj:
279                self.__bufferDescriptor = Buffer(**obj['Buffer'])
280                self.__buffer = None    
281            # Message
282            elif 'Message' in obj:
283                if self.OnMessage:
284                    self.OnMessage(obj)
285
286    def SendTask(self, task, buffer:bytes = None) -> Any:
287        """
288        Sends a task to the scanner.
289        Tasks are general control requests for the scanner. (eg. Camera exposure, or Get Image)
290
291        Creates a task, serializes it, and sends it via the websocket.
292
293        Args:
294            * task (Task): The task to send.
295            * buffer (bytes): The buffer data to send, default is None.
296
297        Returns:
298            Any: The task object that was sent.
299
300        Raises:
301            AssertionError: If the connection is not established.
302        """
303        assert self.__isConnected
304
305        # Update the index
306        task.Index = self.__taskIndex
307        task.Input.Index = self.__taskIndex
308        self.__taskIndex += 1
309
310        # Send the task
311        self.__task_return_event.clear()
312        
313        # Append the task
314        self.__tasks.append(task)
315
316        if buffer == None:
317            self.__SendTask(task)
318        else:
319            self.__SendTaskWithBuffer(task, buffer)
320
321        if task.Output:
322            # Wait for response
323            self.__task_return_event.wait()
324
325        self.__tasks.remove(task)
326
327        return task
328    
329    # Send a task to the scanner
330    def __SendTask(self, task):
331        assert self.__isConnected
332
333        # Serialize the task
334        message = TO_JSON(task.Input)
335        
336        # Build and send the message
337        message = '{"Task":' + message + '}'
338        print('Message: ', message)
339
340        self.websocket.send(message)
341
342    # Send a task with its buffer to the scanner
343    def __SendTaskWithBuffer(self, task:Task, buffer:bytes):
344        assert self.__isConnected
345
346        # Send the task
347        self.__SendTask(task)
348
349        # Build the buffer descriptor
350        bufferSize = len(buffer)
351        bufferDescriptor = Buffer(0, bufferSize, task)
352
353        # Serialize the buffer descriptor
354        bufferMessage = TO_JSON(bufferDescriptor)
355
356        # Send the buffer descriptor
357        bufferMessage = '{"Buffer":' + bufferMessage + '}'
358        self.websocket.send(bufferMessage)
359
360        # The maximum websocket payload size is 32 MB.
361        MAX_SIZE = 32000000
362        sentSize = 0
363
364        # Send all the sub-payloads of the maximum payload size.
365        while sentSize + MAX_SIZE < bufferSize:
366            self.websocket.send(buffer[sentSize:sentSize + MAX_SIZE], websocket.ABNF.OPCODE_BINARY)
367            sentSize += MAX_SIZE
368
369        # Send the remaining data.
370        if sentSize < bufferSize:
371            self.websocket.send(buffer[sentSize:bufferSize], websocket.ABNF.OPCODE_BINARY)
372    
373    def __FindTaskWithIndex(self, index:int) -> Task:
374        # Find the task in the list
375        for i, t in enumerate(self.__tasks):
376            if t.Index == index:
377                return t
378                break
379        return None
380
381    # Dynamically bound functions from three.py
382
383    def add_merge_to_project(self) -> 'Task':
384        """Add a merged scan to the current project."""
385        return Three.add_merge_to_project(self)
386
387    def align(self, source: 'int', target: 'int', rough: 'Align.Rough' = None, fine: 'Align.Fine' = None) -> 'Task':
388        """Align two scan groups."""
389        return Three.align(self, source, target, rough, fine)
390
391    def auto_focus(self, applyAll: 'bool', cameras: 'list[AutoFocus.Camera]' = None) -> 'Task':
392        """Auto focus one or both cameras."""
393        return Three.auto_focus(self, applyAll, cameras)
394
395    def bounding_box(self, selection: 'ScanSelection', axisAligned: 'bool') -> 'Task':
396        """Get the bounding box of a set of scan groups."""
397        return Three.bounding_box(self, selection, axisAligned)
398
399    def calibrate_cameras(self) -> 'Task':
400        """Calibrate the cameras."""
401        return Three.calibrate_cameras(self)
402
403    def calibrate_turntable(self) -> 'Task':
404        """Calibrate the turntable."""
405        return Three.calibrate_turntable(self)
406
407    def calibration_capture_targets(self) -> 'Task':
408        """Get the calibration capture target for each camera calibration capture."""
409        return Three.calibration_capture_targets(self)
410
411    def camera_calibration(self) -> 'Task':
412        """Get the camera calibration descriptor."""
413        return Three.camera_calibration(self)
414
415    def capture_image(self, selection: 'list[int]' = None, codec: 'CaptureImage.Codec' = None, grayscale: 'bool' = None) -> 'Task':
416        """Capture a single Image."""
417        return Three.capture_image(self, selection, codec, grayscale)
418
419    def close_project(self) -> 'Task':
420        """Close the current open project."""
421        return Three.close_project(self)
422
423    def connect_wifi(self, ssid: 'str', password: 'str') -> 'Task':
424        """Connect to a wifi network."""
425        return Three.connect_wifi(self, ssid, password)
426
427    def copy_groups(self, sourceIndexes: 'list[int]' = None, targetIndex: 'int' = None, childPosition: 'int' = None, nameSuffix: 'str' = None, enumerate: 'bool' = None) -> 'Task':
428        """Copy a set of scan groups in the current open project."""
429        return Three.copy_groups(self, sourceIndexes, targetIndex, childPosition, nameSuffix, enumerate)
430
431    def depth_map(self, camera: 'Camera' = None, projector: 'Projector' = None, turntable: 'Turntable' = None, capture: 'Capture' = None, processing: 'Scan.Processing' = None, alignWithScanner: 'bool' = None, centerAtOrigin: 'bool' = None) -> 'Task':
432        """Capture a depth map."""
433        return Three.depth_map(self, camera, projector, turntable, capture, processing, alignWithScanner, centerAtOrigin)
434
435    def detect_calibration_card(self, Input: 'int') -> 'Task':
436        """Detect the calibration card on one or both cameras."""
437        return Three.detect_calibration_card(self, Input)
438
439    def download_project(self, Input: 'int') -> 'Task':
440        """Download a project from the scanner."""
441        return Three.download_project(self, Input)
442
443    def export(self, selection: 'ScanSelection' = None, texture: 'bool' = None, merge: 'bool' = None, format: 'Export.Format' = None, scale: 'float' = None, color: 'Export.Color' = None) -> 'Task':
444        """Export a group of scans."""
445        return Three.export(self, selection, texture, merge, format, scale, color)
446
447    def export_heat_map(self, selection: 'ScanSelection' = None, texture: 'bool' = None, merge: 'bool' = None, format: 'Export.Format' = None, scale: 'float' = None, color: 'Export.Color' = None) -> 'Task':
448        """Export a mesh with vertex colors generated by the 'HeatMap' task."""
449        return Three.export_heat_map(self, selection, texture, merge, format, scale, color)
450
451    def export_logs(self, Input: 'bool' = None) -> 'Task':
452        """Export scanner logs."""
453        return Three.export_logs(self, Input)
454
455    def export_merge(self, selection: 'ScanSelection' = None, texture: 'bool' = None, merge: 'bool' = None, format: 'Export.Format' = None, scale: 'float' = None, color: 'Export.Color' = None) -> 'Task':
456        """Export a merged scan."""
457        return Three.export_merge(self, selection, texture, merge, format, scale, color)
458
459    def flatten_group(self, Input: 'int') -> 'Task':
460        """Flatten a scan group such that it only consists of single scans."""
461        return Three.flatten_group(self, Input)
462
463    def forget_wifi(self) -> 'Task':
464        """Forget all wifi connections."""
465        return Three.forget_wifi(self)
466
467    def has_cameras(self) -> 'Task':
468        """Check if the scanner has working cameras."""
469        return Three.has_cameras(self)
470
471    def has_projector(self) -> 'Task':
472        """Check if the scanner has a working projector."""
473        return Three.has_projector(self)
474
475    def has_turntable(self) -> 'Task':
476        """Check if the scanner is connected to a working turntable."""
477        return Three.has_turntable(self)
478
479    def heat_map(self, sources: 'list[int]' = None, targets: 'list[int]' = None, outlierDistance: 'float' = None) -> 'Task':
480        """Compute the point-to-mesh distances of a source mesh to a target mesh and visualize as a heat map."""
481        return Three.heat_map(self, sources, targets, outlierDistance)
482
483    def import_file(self, name: 'str' = None, scale: 'float' = None, unit: 'Import.Unit' = None, center: 'bool' = None, groupIndex: 'int' = None) -> 'Task':
484        """Import a set of 3D meshes to the current open project.  The meshes must be archived in a ZIP file."""
485        return Three.import_file(self, name, scale, unit, center, groupIndex)
486
487    def list_export_formats(self) -> 'Task':
488        """List all export formats."""
489        return Three.list_export_formats(self)
490
491    def list_groups(self) -> 'Task':
492        """List the scan groups in the current open project."""
493        return Three.list_groups(self)
494
495    def list_network_interfaces(self) -> 'Task':
496        """List available wifi networks."""
497        return Three.list_network_interfaces(self)
498
499    def list_projects(self) -> 'Task':
500        """List all projects."""
501        return Three.list_projects(self)
502
503    def list_scans(self) -> 'Task':
504        """List the scans in the current open project."""
505        return Three.list_scans(self)
506
507    def list_settings(self) -> 'Task':
508        """Get scanner settings."""
509        return Three.list_settings(self)
510
511    def list_wifi(self) -> 'Task':
512        """List available wifi networks."""
513        return Three.list_wifi(self)
514
515    def merge(self, selection: 'ScanSelection' = None, remesh: 'Merge.Remesh' = None, simplify: 'Merge.Simplify' = None, texturize: 'bool' = None) -> 'Task':
516        """Merge two or more scan groups."""
517        return Three.merge(self, selection, remesh, simplify, texturize)
518
519    def merge_data(self, index: 'int', mergeStep: 'ScanData.MergeStep' = None, buffers: 'list[ScanData.Buffer]' = None, metadata: 'list[ScanData.Metadata]' = None) -> 'Task':
520        """Download the raw scan data for the current merge process."""
521        return Three.merge_data(self, index, mergeStep, buffers, metadata)
522
523    def move_group(self, Input: 'list[int]' = None) -> 'Task':
524        """Move a scan group."""
525        return Three.move_group(self, Input)
526
527    def new_group(self, parentIndex: 'int' = None, baseName: 'str' = None, color: 'list[float]' = None, visible: 'bool' = None, collapsed: 'bool' = None, rotation: 'list[float]' = None, translation: 'list[float]' = None) -> 'Task':
528        """Create a new scan group."""
529        return Three.new_group(self, parentIndex, baseName, color, visible, collapsed, rotation, translation)
530
531    def new_project(self, Input: 'str' = None) -> 'Task':
532        """Create a new project."""
533        return Three.new_project(self, Input)
534
535    def new_scan(self, camera: 'Camera' = None, projector: 'Projector' = None, turntable: 'Turntable' = None, capture: 'Capture' = None, processing: 'Scan.Processing' = None, alignWithScanner: 'bool' = None, centerAtOrigin: 'bool' = None) -> 'Task':
536        """Capture a new scan."""
537        return Three.new_scan(self, camera, projector, turntable, capture, processing, alignWithScanner, centerAtOrigin)
538
539    def open_project(self, Input: 'int') -> 'Task':
540        """Open an existing project."""
541        return Three.open_project(self, Input)
542
543    def pop_settings(self, Input: 'bool' = None) -> 'Task':
544        """Pop and restore scanner settings from the settings stack."""
545        return Three.pop_settings(self, Input)
546
547    def push_settings(self) -> 'Task':
548        """Push the current scanner settings to the settings stack."""
549        return Three.push_settings(self)
550
551    def reboot(self) -> 'Task':
552        """Reboot the scanner."""
553        return Three.reboot(self)
554
555    def remove_groups(self, Input: 'list[int]' = None) -> 'Task':
556        """Remove selected scan groups."""
557        return Three.remove_groups(self, Input)
558
559    def remove_projects(self, Input: 'list[int]' = None) -> 'Task':
560        """Remove selected projects."""
561        return Three.remove_projects(self, Input)
562
563    def restore_factory_calibration(self) -> 'Task':
564        """Restore factory calibration."""
565        return Three.restore_factory_calibration(self)
566
567    def rotate_turntable(self, Input: 'int') -> 'Task':
568        """Rotate the turntable."""
569        return Three.rotate_turntable(self, Input)
570
571    def scan_data(self, index: 'int', mergeStep: 'ScanData.MergeStep' = None, buffers: 'list[ScanData.Buffer]' = None, metadata: 'list[ScanData.Metadata]' = None) -> 'Task':
572        """Download the raw scan data for a scan in the current open project."""
573        return Three.scan_data(self, index, mergeStep, buffers, metadata)
574
575    def set_cameras(self, selection: 'list[int]' = None, autoExposure: 'bool' = None, exposure: 'int' = None, analogGain: 'float' = None, digitalGain: 'int' = None, focus: 'int' = None) -> 'Task':
576        """Apply camera settings to one or both cameras."""
577        return Three.set_cameras(self, selection, autoExposure, exposure, analogGain, digitalGain, focus)
578
579    def set_group(self, index: 'int', name: 'str' = None, color: 'list[float]' = None, visible: 'bool' = None, collapsed: 'bool' = None, rotation: 'list[float]' = None, translation: 'list[float]' = None) -> 'Task':
580        """Set scan group properties."""
581        return Three.set_group(self, index, name, color, visible, collapsed, rotation, translation)
582
583    def set_project(self, index: 'int' = None, name: 'str' = None) -> 'Task':
584        """Apply settings to the current open project."""
585        return Three.set_project(self, index, name)
586
587    def set_projector(self, on: 'bool' = None, brightness: 'float' = None, pattern: 'Projector.Pattern' = None, image: 'Projector.Image' = None, color: 'list[float]' = None, buffer: 'bytes' = None) -> 'Task':
588        """Apply projector settings."""
589        return Three.set_projector(self, on, brightness, pattern, image, color, buffer)
590
591    def shutdown(self) -> 'Task':
592        """Shutdown the scanner."""
593        return Three.shutdown(self)
594
595    def split_group(self, Input: 'int') -> 'Task':
596        """Split a scan group (ie. move its subgroups to its parent group)."""
597        return Three.split_group(self, Input)
598
599    def start_video(self) -> 'Task':
600        """Start the video stream."""
601        return Three.start_video(self)
602
603    def stop_video(self) -> 'Task':
604        """Stop the video stream."""
605        return Three.stop_video(self)
606
607    def system_info(self, updateMajor: 'bool' = None, updateNightly: 'bool' = None) -> 'Task':
608        """Get system information."""
609        return Three.system_info(self, updateMajor, updateNightly)
610
611    def transform_group(self, index: 'int', name: 'str' = None, color: 'list[float]' = None, visible: 'bool' = None, collapsed: 'bool' = None, rotation: 'list[float]' = None, translation: 'list[float]' = None) -> 'Task':
612        """Apply a rigid transformation to a group."""
613        return Three.transform_group(self, index, name, color, visible, collapsed, rotation, translation)
614
615    def turntable_calibration(self) -> 'Task':
616        """Get the turntable calibration descriptor."""
617        return Three.turntable_calibration(self)
618
619    def update_settings(self, advanced: 'Advanced' = None, camera: 'Camera' = None, capture: 'Capture' = None, i18n: 'I18n' = None, projector: 'Projector' = None, style: 'Style' = None, turntable: 'Turntable' = None, tutorials: 'Tutorials' = None, viewer: 'Viewer' = None, software: 'Software' = None) -> 'Task':
620        """Update scanner settings."""
621        return Three.update_settings(self, advanced, camera, capture, i18n, projector, style, turntable, tutorials, viewer, software)
622
623    def upload_project(self, buffer: 'bytes') -> 'Task':
624        """Upload a project to the scanner."""
625        return Three.upload_project(self, buffer)
class Scanner:
 49class Scanner:
 50    """
 51    Main class to manage and communicate with the Matter and Form THREE 3D Scanner via websocket.
 52
 53    Attributes:
 54        * OnTask (Optional[Callable[[Task], None]]): Callback for any task related messages, default is None. Will fire for task complete, progress, and error.
 55        * OnMessage (Optional[Callable[[str], None]]): Callback function to handle messages, default is None. Messages are any calls related to the system that are not tasks or buffers. Eg. {"Message":{"HasTurntable":true}}
 56        * OnBuffer (Optional[Callable[[Any, bytes], None]]): Callback Function to handle buffer data, default is None. Buffers can be image or scan data. These are binary formats that are sent separately from tasks. Buffers will contain a header that describes the buffer data size. Please note that websocket buffers are limited in size and need to be sent in chunks.
 57    """
 58    
 59    __bufferDescriptor = None
 60    __buffer = None
 61    __error = None
 62    __taskIndex:int = 0
 63    __tasks:List[Task] = []
 64
 65
 66    def __init__(self,
 67        OnTask: Optional[Callable[[Task], None]] = None,
 68        OnMessage: Optional[Callable[[str], None]] = None,
 69        OnBuffer: Optional[Callable[[Any, bytes], None]] = None,
 70        ):
 71        """
 72        Initializes the Scanner object.
 73
 74        Args:
 75            * OnTask (Optional[Callable[[Task], None]]): Callback for any task related messages, default is None. Will fire for task complete, progress, and error.
 76            * OnMessage (Optional[Callable[[str], None]]): Callback function to handle messages, default is None. Messages are any calls related to the system that are not tasks or buffers. Eg. {"Message":{"HasTurntable":true}}
 77            * OnBuffer (Optional[Callable[[Any, bytes], None]]): Callback Function to handle buffer data, default is None. Buffers can be image or scan data. These are binary formats that are sent separately from tasks. Buffers will contain a header that describes the buffer data size. Please note that websocket buffers are limited in size and need to be sent in chunks.
 78        """
 79        self.__isConnected = False
 80
 81        self.OnTask = OnTask
 82        self.OnMessage = OnMessage
 83        self.OnBuffer = OnBuffer
 84        
 85        self.__task_return_event = threading.Event()
 86        
 87        # Dynamically add methods from Three to Scanner
 88        # self._add_three_methods()
 89
 90    # def _add_three_methods(self):
 91    #     """
 92    #     Dynamically adds functions from the three_methods module to the Scanner class.
 93    #     """
 94    #     for name, func in inspect.getmembers(Three, predicate=inspect.isfunction):
 95    #         if not name.startswith('_'):
 96    #             setattr(self, name, func.__get__(self, self.__class__))
 97
 98
 99    def Connect(self, URI:str, timeoutSec=5) -> bool:
100        """
101        Attempts to connect to the scanner using the specified URI and timeout.
102
103        Args:
104            * URI (str): The URI of the websocket server.
105            * timeoutSec (int): Timeout in seconds, default is 5.
106
107        Returns:
108            bool: True if connection is successful, raises Exception otherwise.
109
110        Raises:
111            Exception: If connection fails within the timeout or due to an error.
112        """
113        print('Connecting to: ', URI)
114        self.__URI = URI
115        self.__isConnected = False
116        self.__error = None
117
118        self.__serverVersion__= None
119
120        self.websocket = websocket.WebSocketApp(self.__URI,
121                              on_open=self.__OnOpen,
122                              on_close=self.__OnClose,
123                              on_error=self.__OnError,
124                              on_message=self.__OnMessage,
125                              )
126        
127        wst = threading.Thread(target=self.websocket.run_forever)
128        wst.daemon = True
129        wst.start()
130
131        # Wait for connection
132        start = time.time()
133        while time.time() < start + timeoutSec:
134            if self.__isConnected:
135                # Not checking versions => return True
136                    return True
137            elif self.__error:
138                raise Exception(self.__error)
139            time.sleep(0.1)
140        
141        raise Exception('Connection timeout')
142        
143    def Disconnect(self) -> None:
144        """
145        Close the websocket connection.
146        """
147        if self.__isConnected:
148            # Close the connection
149            self.websocket.close()
150            # Wait for the connection to be closed.
151            while self.__isConnected:
152                time.sleep(0.1)
153
154    def IsConnected(self)-> bool:
155        """
156        Checks if the scanner is currently connected.
157
158        Returns:
159            bool: True if connected, False otherwise.
160        """
161        return self.__isConnected
162    
163    def __callback(self, callback, *args) -> None:
164        if callback:
165                callback(self, *args)
166
167    # Called when the connection is opened
168    def __OnOpen(self, ws) -> None:
169        """
170        Callback function for when the websocket connection is successfully opened.
171
172        Prints a success message to the console.
173
174        Args:
175            ws: The websocket object.
176        """
177        self.__isConnected = True
178        print('Connected to: ', self.__URI)
179
180    # Called when the connection is closed
181    def __OnClose(self, ws, close_status_code, close_msg):
182        """
183        Callback function for when the websocket connection is closed.
184
185        Prints a disconnect message to the console.
186
187        Args:
188            ws: The websocket object.
189            close_status_code: The code indicating why the websocket was closed.
190            close_msg: Additional message about why the websocket was closed.
191        """
192        if self.__isConnected:
193            print('Disconnected')
194        self.__isConnected = False
195
196    # Called when an error happens
197    def __OnError(self, ws, error) -> None:
198        """
199        Callback function for when an error occurs in the websocket connection.
200
201        Prints an error message to the console and stores the error for reference.
202
203        Args:
204            ws: The websocket object.
205            error: The error that occurred.
206        """
207        if self.__isConnected:
208            print('Error: ', error)    
209        else:
210            self.__error = error
211        
212    # Called when a message arrives on the connection
213    def __OnMessage(self, ws, message) -> None:
214        """
215        Callback function for handling messages received via the websocket.
216
217        Determines the type of message received (Task, Buffer, or general Message) and
218        triggers the corresponding handler function if one is set.
219
220        Args:
221            ws: The websocket object.
222            message: The raw message received, which can be either a byte string or a JSON string.
223        """
224        # Bytes ?
225        if isinstance(message, bytes):
226            if self.OnBuffer:
227                
228                if self.__buffer:
229                    self.__buffer += message
230                else:
231                    self.__buffer = message
232                if self.__bufferDescriptor.Size == len(self.__buffer):
233                    self.OnBuffer(self.__bufferDescriptor, message)
234                    self.__bufferDescriptor = None 
235                    self.__buffer = None
236        else:
237            obj = json.loads(message)              
238        
239            # Task
240            if 'Task' in obj:
241                # Create the task from the message
242                task = Task(**obj['Task'])
243                
244                if (task.Progress):
245                    # Extract the first (and only) item from the task.Progress dictionary
246                    # TODO Duct tape fix due to schema weirdness for progress
247                    key, process = next(iter(task.Progress.items()))
248                    task.Progress = type('Progress', (object,), {
249                        'current': process["current"],
250                        'step': process["step"],
251                        'total': process["total"]
252                    })()
253
254                # Find the original task for reference
255                inputTask = self.__FindTaskWithIndex(task.Index)
256                if inputTask == None:
257                    raise Exception('Task not found')
258                    
259                if task.Error:
260                    inputTask.Error = task.Error
261                    self.__OnError(self.websocket, task.Error)
262                    self.__task_return_event.set()
263                    
264                # If assigned => Call the handler
265                if self.OnTask:
266                    self.OnTask(task)
267                
268                
269                # If waiting for a response, set the response and notify
270                if (task.State == TaskState.Completed.value):
271                    if task.Output:
272                        inputTask.Output = task.Output
273                    self.__task_return_event.set()
274                elif (task.State == TaskState.Failed.value):
275                    inputTask.Error = task.Error
276                    self.__task_return_event
277                    
278            # Buffer
279            elif 'Buffer' in obj:
280                self.__bufferDescriptor = Buffer(**obj['Buffer'])
281                self.__buffer = None    
282            # Message
283            elif 'Message' in obj:
284                if self.OnMessage:
285                    self.OnMessage(obj)
286
287    def SendTask(self, task, buffer:bytes = None) -> Any:
288        """
289        Sends a task to the scanner.
290        Tasks are general control requests for the scanner. (eg. Camera exposure, or Get Image)
291
292        Creates a task, serializes it, and sends it via the websocket.
293
294        Args:
295            * task (Task): The task to send.
296            * buffer (bytes): The buffer data to send, default is None.
297
298        Returns:
299            Any: The task object that was sent.
300
301        Raises:
302            AssertionError: If the connection is not established.
303        """
304        assert self.__isConnected
305
306        # Update the index
307        task.Index = self.__taskIndex
308        task.Input.Index = self.__taskIndex
309        self.__taskIndex += 1
310
311        # Send the task
312        self.__task_return_event.clear()
313        
314        # Append the task
315        self.__tasks.append(task)
316
317        if buffer == None:
318            self.__SendTask(task)
319        else:
320            self.__SendTaskWithBuffer(task, buffer)
321
322        if task.Output:
323            # Wait for response
324            self.__task_return_event.wait()
325
326        self.__tasks.remove(task)
327
328        return task
329    
330    # Send a task to the scanner
331    def __SendTask(self, task):
332        assert self.__isConnected
333
334        # Serialize the task
335        message = TO_JSON(task.Input)
336        
337        # Build and send the message
338        message = '{"Task":' + message + '}'
339        print('Message: ', message)
340
341        self.websocket.send(message)
342
343    # Send a task with its buffer to the scanner
344    def __SendTaskWithBuffer(self, task:Task, buffer:bytes):
345        assert self.__isConnected
346
347        # Send the task
348        self.__SendTask(task)
349
350        # Build the buffer descriptor
351        bufferSize = len(buffer)
352        bufferDescriptor = Buffer(0, bufferSize, task)
353
354        # Serialize the buffer descriptor
355        bufferMessage = TO_JSON(bufferDescriptor)
356
357        # Send the buffer descriptor
358        bufferMessage = '{"Buffer":' + bufferMessage + '}'
359        self.websocket.send(bufferMessage)
360
361        # The maximum websocket payload size is 32 MB.
362        MAX_SIZE = 32000000
363        sentSize = 0
364
365        # Send all the sub-payloads of the maximum payload size.
366        while sentSize + MAX_SIZE < bufferSize:
367            self.websocket.send(buffer[sentSize:sentSize + MAX_SIZE], websocket.ABNF.OPCODE_BINARY)
368            sentSize += MAX_SIZE
369
370        # Send the remaining data.
371        if sentSize < bufferSize:
372            self.websocket.send(buffer[sentSize:bufferSize], websocket.ABNF.OPCODE_BINARY)
373    
374    def __FindTaskWithIndex(self, index:int) -> Task:
375        # Find the task in the list
376        for i, t in enumerate(self.__tasks):
377            if t.Index == index:
378                return t
379                break
380        return None
381
382    # Dynamically bound functions from three.py
383
384    def add_merge_to_project(self) -> 'Task':
385        """Add a merged scan to the current project."""
386        return Three.add_merge_to_project(self)
387
388    def align(self, source: 'int', target: 'int', rough: 'Align.Rough' = None, fine: 'Align.Fine' = None) -> 'Task':
389        """Align two scan groups."""
390        return Three.align(self, source, target, rough, fine)
391
392    def auto_focus(self, applyAll: 'bool', cameras: 'list[AutoFocus.Camera]' = None) -> 'Task':
393        """Auto focus one or both cameras."""
394        return Three.auto_focus(self, applyAll, cameras)
395
396    def bounding_box(self, selection: 'ScanSelection', axisAligned: 'bool') -> 'Task':
397        """Get the bounding box of a set of scan groups."""
398        return Three.bounding_box(self, selection, axisAligned)
399
400    def calibrate_cameras(self) -> 'Task':
401        """Calibrate the cameras."""
402        return Three.calibrate_cameras(self)
403
404    def calibrate_turntable(self) -> 'Task':
405        """Calibrate the turntable."""
406        return Three.calibrate_turntable(self)
407
408    def calibration_capture_targets(self) -> 'Task':
409        """Get the calibration capture target for each camera calibration capture."""
410        return Three.calibration_capture_targets(self)
411
412    def camera_calibration(self) -> 'Task':
413        """Get the camera calibration descriptor."""
414        return Three.camera_calibration(self)
415
416    def capture_image(self, selection: 'list[int]' = None, codec: 'CaptureImage.Codec' = None, grayscale: 'bool' = None) -> 'Task':
417        """Capture a single Image."""
418        return Three.capture_image(self, selection, codec, grayscale)
419
420    def close_project(self) -> 'Task':
421        """Close the current open project."""
422        return Three.close_project(self)
423
424    def connect_wifi(self, ssid: 'str', password: 'str') -> 'Task':
425        """Connect to a wifi network."""
426        return Three.connect_wifi(self, ssid, password)
427
428    def copy_groups(self, sourceIndexes: 'list[int]' = None, targetIndex: 'int' = None, childPosition: 'int' = None, nameSuffix: 'str' = None, enumerate: 'bool' = None) -> 'Task':
429        """Copy a set of scan groups in the current open project."""
430        return Three.copy_groups(self, sourceIndexes, targetIndex, childPosition, nameSuffix, enumerate)
431
432    def depth_map(self, camera: 'Camera' = None, projector: 'Projector' = None, turntable: 'Turntable' = None, capture: 'Capture' = None, processing: 'Scan.Processing' = None, alignWithScanner: 'bool' = None, centerAtOrigin: 'bool' = None) -> 'Task':
433        """Capture a depth map."""
434        return Three.depth_map(self, camera, projector, turntable, capture, processing, alignWithScanner, centerAtOrigin)
435
436    def detect_calibration_card(self, Input: 'int') -> 'Task':
437        """Detect the calibration card on one or both cameras."""
438        return Three.detect_calibration_card(self, Input)
439
440    def download_project(self, Input: 'int') -> 'Task':
441        """Download a project from the scanner."""
442        return Three.download_project(self, Input)
443
444    def export(self, selection: 'ScanSelection' = None, texture: 'bool' = None, merge: 'bool' = None, format: 'Export.Format' = None, scale: 'float' = None, color: 'Export.Color' = None) -> 'Task':
445        """Export a group of scans."""
446        return Three.export(self, selection, texture, merge, format, scale, color)
447
448    def export_heat_map(self, selection: 'ScanSelection' = None, texture: 'bool' = None, merge: 'bool' = None, format: 'Export.Format' = None, scale: 'float' = None, color: 'Export.Color' = None) -> 'Task':
449        """Export a mesh with vertex colors generated by the 'HeatMap' task."""
450        return Three.export_heat_map(self, selection, texture, merge, format, scale, color)
451
452    def export_logs(self, Input: 'bool' = None) -> 'Task':
453        """Export scanner logs."""
454        return Three.export_logs(self, Input)
455
456    def export_merge(self, selection: 'ScanSelection' = None, texture: 'bool' = None, merge: 'bool' = None, format: 'Export.Format' = None, scale: 'float' = None, color: 'Export.Color' = None) -> 'Task':
457        """Export a merged scan."""
458        return Three.export_merge(self, selection, texture, merge, format, scale, color)
459
460    def flatten_group(self, Input: 'int') -> 'Task':
461        """Flatten a scan group such that it only consists of single scans."""
462        return Three.flatten_group(self, Input)
463
464    def forget_wifi(self) -> 'Task':
465        """Forget all wifi connections."""
466        return Three.forget_wifi(self)
467
468    def has_cameras(self) -> 'Task':
469        """Check if the scanner has working cameras."""
470        return Three.has_cameras(self)
471
472    def has_projector(self) -> 'Task':
473        """Check if the scanner has a working projector."""
474        return Three.has_projector(self)
475
476    def has_turntable(self) -> 'Task':
477        """Check if the scanner is connected to a working turntable."""
478        return Three.has_turntable(self)
479
480    def heat_map(self, sources: 'list[int]' = None, targets: 'list[int]' = None, outlierDistance: 'float' = None) -> 'Task':
481        """Compute the point-to-mesh distances of a source mesh to a target mesh and visualize as a heat map."""
482        return Three.heat_map(self, sources, targets, outlierDistance)
483
484    def import_file(self, name: 'str' = None, scale: 'float' = None, unit: 'Import.Unit' = None, center: 'bool' = None, groupIndex: 'int' = None) -> 'Task':
485        """Import a set of 3D meshes to the current open project.  The meshes must be archived in a ZIP file."""
486        return Three.import_file(self, name, scale, unit, center, groupIndex)
487
488    def list_export_formats(self) -> 'Task':
489        """List all export formats."""
490        return Three.list_export_formats(self)
491
492    def list_groups(self) -> 'Task':
493        """List the scan groups in the current open project."""
494        return Three.list_groups(self)
495
496    def list_network_interfaces(self) -> 'Task':
497        """List available wifi networks."""
498        return Three.list_network_interfaces(self)
499
500    def list_projects(self) -> 'Task':
501        """List all projects."""
502        return Three.list_projects(self)
503
504    def list_scans(self) -> 'Task':
505        """List the scans in the current open project."""
506        return Three.list_scans(self)
507
508    def list_settings(self) -> 'Task':
509        """Get scanner settings."""
510        return Three.list_settings(self)
511
512    def list_wifi(self) -> 'Task':
513        """List available wifi networks."""
514        return Three.list_wifi(self)
515
516    def merge(self, selection: 'ScanSelection' = None, remesh: 'Merge.Remesh' = None, simplify: 'Merge.Simplify' = None, texturize: 'bool' = None) -> 'Task':
517        """Merge two or more scan groups."""
518        return Three.merge(self, selection, remesh, simplify, texturize)
519
520    def merge_data(self, index: 'int', mergeStep: 'ScanData.MergeStep' = None, buffers: 'list[ScanData.Buffer]' = None, metadata: 'list[ScanData.Metadata]' = None) -> 'Task':
521        """Download the raw scan data for the current merge process."""
522        return Three.merge_data(self, index, mergeStep, buffers, metadata)
523
524    def move_group(self, Input: 'list[int]' = None) -> 'Task':
525        """Move a scan group."""
526        return Three.move_group(self, Input)
527
528    def new_group(self, parentIndex: 'int' = None, baseName: 'str' = None, color: 'list[float]' = None, visible: 'bool' = None, collapsed: 'bool' = None, rotation: 'list[float]' = None, translation: 'list[float]' = None) -> 'Task':
529        """Create a new scan group."""
530        return Three.new_group(self, parentIndex, baseName, color, visible, collapsed, rotation, translation)
531
532    def new_project(self, Input: 'str' = None) -> 'Task':
533        """Create a new project."""
534        return Three.new_project(self, Input)
535
536    def new_scan(self, camera: 'Camera' = None, projector: 'Projector' = None, turntable: 'Turntable' = None, capture: 'Capture' = None, processing: 'Scan.Processing' = None, alignWithScanner: 'bool' = None, centerAtOrigin: 'bool' = None) -> 'Task':
537        """Capture a new scan."""
538        return Three.new_scan(self, camera, projector, turntable, capture, processing, alignWithScanner, centerAtOrigin)
539
540    def open_project(self, Input: 'int') -> 'Task':
541        """Open an existing project."""
542        return Three.open_project(self, Input)
543
544    def pop_settings(self, Input: 'bool' = None) -> 'Task':
545        """Pop and restore scanner settings from the settings stack."""
546        return Three.pop_settings(self, Input)
547
548    def push_settings(self) -> 'Task':
549        """Push the current scanner settings to the settings stack."""
550        return Three.push_settings(self)
551
552    def reboot(self) -> 'Task':
553        """Reboot the scanner."""
554        return Three.reboot(self)
555
556    def remove_groups(self, Input: 'list[int]' = None) -> 'Task':
557        """Remove selected scan groups."""
558        return Three.remove_groups(self, Input)
559
560    def remove_projects(self, Input: 'list[int]' = None) -> 'Task':
561        """Remove selected projects."""
562        return Three.remove_projects(self, Input)
563
564    def restore_factory_calibration(self) -> 'Task':
565        """Restore factory calibration."""
566        return Three.restore_factory_calibration(self)
567
568    def rotate_turntable(self, Input: 'int') -> 'Task':
569        """Rotate the turntable."""
570        return Three.rotate_turntable(self, Input)
571
572    def scan_data(self, index: 'int', mergeStep: 'ScanData.MergeStep' = None, buffers: 'list[ScanData.Buffer]' = None, metadata: 'list[ScanData.Metadata]' = None) -> 'Task':
573        """Download the raw scan data for a scan in the current open project."""
574        return Three.scan_data(self, index, mergeStep, buffers, metadata)
575
576    def set_cameras(self, selection: 'list[int]' = None, autoExposure: 'bool' = None, exposure: 'int' = None, analogGain: 'float' = None, digitalGain: 'int' = None, focus: 'int' = None) -> 'Task':
577        """Apply camera settings to one or both cameras."""
578        return Three.set_cameras(self, selection, autoExposure, exposure, analogGain, digitalGain, focus)
579
580    def set_group(self, index: 'int', name: 'str' = None, color: 'list[float]' = None, visible: 'bool' = None, collapsed: 'bool' = None, rotation: 'list[float]' = None, translation: 'list[float]' = None) -> 'Task':
581        """Set scan group properties."""
582        return Three.set_group(self, index, name, color, visible, collapsed, rotation, translation)
583
584    def set_project(self, index: 'int' = None, name: 'str' = None) -> 'Task':
585        """Apply settings to the current open project."""
586        return Three.set_project(self, index, name)
587
588    def set_projector(self, on: 'bool' = None, brightness: 'float' = None, pattern: 'Projector.Pattern' = None, image: 'Projector.Image' = None, color: 'list[float]' = None, buffer: 'bytes' = None) -> 'Task':
589        """Apply projector settings."""
590        return Three.set_projector(self, on, brightness, pattern, image, color, buffer)
591
592    def shutdown(self) -> 'Task':
593        """Shutdown the scanner."""
594        return Three.shutdown(self)
595
596    def split_group(self, Input: 'int') -> 'Task':
597        """Split a scan group (ie. move its subgroups to its parent group)."""
598        return Three.split_group(self, Input)
599
600    def start_video(self) -> 'Task':
601        """Start the video stream."""
602        return Three.start_video(self)
603
604    def stop_video(self) -> 'Task':
605        """Stop the video stream."""
606        return Three.stop_video(self)
607
608    def system_info(self, updateMajor: 'bool' = None, updateNightly: 'bool' = None) -> 'Task':
609        """Get system information."""
610        return Three.system_info(self, updateMajor, updateNightly)
611
612    def transform_group(self, index: 'int', name: 'str' = None, color: 'list[float]' = None, visible: 'bool' = None, collapsed: 'bool' = None, rotation: 'list[float]' = None, translation: 'list[float]' = None) -> 'Task':
613        """Apply a rigid transformation to a group."""
614        return Three.transform_group(self, index, name, color, visible, collapsed, rotation, translation)
615
616    def turntable_calibration(self) -> 'Task':
617        """Get the turntable calibration descriptor."""
618        return Three.turntable_calibration(self)
619
620    def update_settings(self, advanced: 'Advanced' = None, camera: 'Camera' = None, capture: 'Capture' = None, i18n: 'I18n' = None, projector: 'Projector' = None, style: 'Style' = None, turntable: 'Turntable' = None, tutorials: 'Tutorials' = None, viewer: 'Viewer' = None, software: 'Software' = None) -> 'Task':
621        """Update scanner settings."""
622        return Three.update_settings(self, advanced, camera, capture, i18n, projector, style, turntable, tutorials, viewer, software)
623
624    def upload_project(self, buffer: 'bytes') -> 'Task':
625        """Upload a project to the scanner."""
626        return Three.upload_project(self, buffer)

Main class to manage and communicate with the Matter and Form THREE 3D Scanner via websocket.

Attributes: * OnTask (Optional[Callable[[Task], None]]): Callback for any task related messages, default is None. Will fire for task complete, progress, and error. * OnMessage (Optional[Callable[[str], None]]): Callback function to handle messages, default is None. Messages are any calls related to the system that are not tasks or buffers. Eg. {"Message":{"HasTurntable":true}} * OnBuffer (Optional[Callable[[Any, bytes], None]]): Callback Function to handle buffer data, default is None. Buffers can be image or scan data. These are binary formats that are sent separately from tasks. Buffers will contain a header that describes the buffer data size. Please note that websocket buffers are limited in size and need to be sent in chunks.

Scanner( OnTask: Optional[Callable[[MF.V3.Task.Task], NoneType]] = None, OnMessage: Optional[Callable[[str], NoneType]] = None, OnBuffer: Optional[Callable[[Any, bytes], NoneType]] = None)
66    def __init__(self,
67        OnTask: Optional[Callable[[Task], None]] = None,
68        OnMessage: Optional[Callable[[str], None]] = None,
69        OnBuffer: Optional[Callable[[Any, bytes], None]] = None,
70        ):
71        """
72        Initializes the Scanner object.
73
74        Args:
75            * OnTask (Optional[Callable[[Task], None]]): Callback for any task related messages, default is None. Will fire for task complete, progress, and error.
76            * OnMessage (Optional[Callable[[str], None]]): Callback function to handle messages, default is None. Messages are any calls related to the system that are not tasks or buffers. Eg. {"Message":{"HasTurntable":true}}
77            * OnBuffer (Optional[Callable[[Any, bytes], None]]): Callback Function to handle buffer data, default is None. Buffers can be image or scan data. These are binary formats that are sent separately from tasks. Buffers will contain a header that describes the buffer data size. Please note that websocket buffers are limited in size and need to be sent in chunks.
78        """
79        self.__isConnected = False
80
81        self.OnTask = OnTask
82        self.OnMessage = OnMessage
83        self.OnBuffer = OnBuffer
84        
85        self.__task_return_event = threading.Event()
86        
87        # Dynamically add methods from Three to Scanner
88        # self._add_three_methods()

Initializes the Scanner object.

Args: * OnTask (Optional[Callable[[Task], None]]): Callback for any task related messages, default is None. Will fire for task complete, progress, and error. * OnMessage (Optional[Callable[[str], None]]): Callback function to handle messages, default is None. Messages are any calls related to the system that are not tasks or buffers. Eg. {"Message":{"HasTurntable":true}} * OnBuffer (Optional[Callable[[Any, bytes], None]]): Callback Function to handle buffer data, default is None. Buffers can be image or scan data. These are binary formats that are sent separately from tasks. Buffers will contain a header that describes the buffer data size. Please note that websocket buffers are limited in size and need to be sent in chunks.

OnTask
OnMessage
OnBuffer
def Connect(self, URI: str, timeoutSec=5) -> bool:
 99    def Connect(self, URI:str, timeoutSec=5) -> bool:
100        """
101        Attempts to connect to the scanner using the specified URI and timeout.
102
103        Args:
104            * URI (str): The URI of the websocket server.
105            * timeoutSec (int): Timeout in seconds, default is 5.
106
107        Returns:
108            bool: True if connection is successful, raises Exception otherwise.
109
110        Raises:
111            Exception: If connection fails within the timeout or due to an error.
112        """
113        print('Connecting to: ', URI)
114        self.__URI = URI
115        self.__isConnected = False
116        self.__error = None
117
118        self.__serverVersion__= None
119
120        self.websocket = websocket.WebSocketApp(self.__URI,
121                              on_open=self.__OnOpen,
122                              on_close=self.__OnClose,
123                              on_error=self.__OnError,
124                              on_message=self.__OnMessage,
125                              )
126        
127        wst = threading.Thread(target=self.websocket.run_forever)
128        wst.daemon = True
129        wst.start()
130
131        # Wait for connection
132        start = time.time()
133        while time.time() < start + timeoutSec:
134            if self.__isConnected:
135                # Not checking versions => return True
136                    return True
137            elif self.__error:
138                raise Exception(self.__error)
139            time.sleep(0.1)
140        
141        raise Exception('Connection timeout')

Attempts to connect to the scanner using the specified URI and timeout.

Args: * URI (str): The URI of the websocket server. * timeoutSec (int): Timeout in seconds, default is 5.

Returns: bool: True if connection is successful, raises Exception otherwise.

Raises: Exception: If connection fails within the timeout or due to an error.

def Disconnect(self) -> None:
143    def Disconnect(self) -> None:
144        """
145        Close the websocket connection.
146        """
147        if self.__isConnected:
148            # Close the connection
149            self.websocket.close()
150            # Wait for the connection to be closed.
151            while self.__isConnected:
152                time.sleep(0.1)

Close the websocket connection.

def IsConnected(self) -> bool:
154    def IsConnected(self)-> bool:
155        """
156        Checks if the scanner is currently connected.
157
158        Returns:
159            bool: True if connected, False otherwise.
160        """
161        return self.__isConnected

Checks if the scanner is currently connected.

Returns: bool: True if connected, False otherwise.

def SendTask(self, task, buffer: bytes = None) -> Any:
287    def SendTask(self, task, buffer:bytes = None) -> Any:
288        """
289        Sends a task to the scanner.
290        Tasks are general control requests for the scanner. (eg. Camera exposure, or Get Image)
291
292        Creates a task, serializes it, and sends it via the websocket.
293
294        Args:
295            * task (Task): The task to send.
296            * buffer (bytes): The buffer data to send, default is None.
297
298        Returns:
299            Any: The task object that was sent.
300
301        Raises:
302            AssertionError: If the connection is not established.
303        """
304        assert self.__isConnected
305
306        # Update the index
307        task.Index = self.__taskIndex
308        task.Input.Index = self.__taskIndex
309        self.__taskIndex += 1
310
311        # Send the task
312        self.__task_return_event.clear()
313        
314        # Append the task
315        self.__tasks.append(task)
316
317        if buffer == None:
318            self.__SendTask(task)
319        else:
320            self.__SendTaskWithBuffer(task, buffer)
321
322        if task.Output:
323            # Wait for response
324            self.__task_return_event.wait()
325
326        self.__tasks.remove(task)
327
328        return task

Sends a task to the scanner. Tasks are general control requests for the scanner. (eg. Camera exposure, or Get Image)

Creates a task, serializes it, and sends it via the websocket.

Args: * task (Task): The task to send. * buffer (bytes): The buffer data to send, default is None.

Returns: Any: The task object that was sent.

Raises: AssertionError: If the connection is not established.

def add_merge_to_project(self) -> MF.V3.Task.Task:
384    def add_merge_to_project(self) -> 'Task':
385        """Add a merged scan to the current project."""
386        return Three.add_merge_to_project(self)

Add a merged scan to the current project.

def align( self, source: int, target: int, rough: MF.V3.Settings.Align.Align.Rough = None, fine: MF.V3.Settings.Align.Align.Fine = None) -> MF.V3.Task.Task:
388    def align(self, source: 'int', target: 'int', rough: 'Align.Rough' = None, fine: 'Align.Fine' = None) -> 'Task':
389        """Align two scan groups."""
390        return Three.align(self, source, target, rough, fine)

Align two scan groups.

def auto_focus( self, applyAll: bool, cameras: list[MF.V3.Settings.AutoFocus.AutoFocus.Camera] = None) -> MF.V3.Task.Task:
392    def auto_focus(self, applyAll: 'bool', cameras: 'list[AutoFocus.Camera]' = None) -> 'Task':
393        """Auto focus one or both cameras."""
394        return Three.auto_focus(self, applyAll, cameras)

Auto focus one or both cameras.

def bounding_box( self, selection: MF.V3.Settings.ScanSelection.ScanSelection, axisAligned: bool) -> MF.V3.Task.Task:
396    def bounding_box(self, selection: 'ScanSelection', axisAligned: 'bool') -> 'Task':
397        """Get the bounding box of a set of scan groups."""
398        return Three.bounding_box(self, selection, axisAligned)

Get the bounding box of a set of scan groups.

def calibrate_cameras(self) -> MF.V3.Task.Task:
400    def calibrate_cameras(self) -> 'Task':
401        """Calibrate the cameras."""
402        return Three.calibrate_cameras(self)

Calibrate the cameras.

def calibrate_turntable(self) -> MF.V3.Task.Task:
404    def calibrate_turntable(self) -> 'Task':
405        """Calibrate the turntable."""
406        return Three.calibrate_turntable(self)

Calibrate the turntable.

def calibration_capture_targets(self) -> MF.V3.Task.Task:
408    def calibration_capture_targets(self) -> 'Task':
409        """Get the calibration capture target for each camera calibration capture."""
410        return Three.calibration_capture_targets(self)

Get the calibration capture target for each camera calibration capture.

def camera_calibration(self) -> MF.V3.Task.Task:
412    def camera_calibration(self) -> 'Task':
413        """Get the camera calibration descriptor."""
414        return Three.camera_calibration(self)

Get the camera calibration descriptor.

def capture_image( self, selection: list[int] = None, codec: MF.V3.Settings.CaptureImage.CaptureImage.Codec = None, grayscale: bool = None) -> MF.V3.Task.Task:
416    def capture_image(self, selection: 'list[int]' = None, codec: 'CaptureImage.Codec' = None, grayscale: 'bool' = None) -> 'Task':
417        """Capture a single Image."""
418        return Three.capture_image(self, selection, codec, grayscale)

Capture a single Image.

def close_project(self) -> MF.V3.Task.Task:
420    def close_project(self) -> 'Task':
421        """Close the current open project."""
422        return Three.close_project(self)

Close the current open project.

def connect_wifi(self, ssid: str, password: str) -> MF.V3.Task.Task:
424    def connect_wifi(self, ssid: 'str', password: 'str') -> 'Task':
425        """Connect to a wifi network."""
426        return Three.connect_wifi(self, ssid, password)

Connect to a wifi network.

def copy_groups( self, sourceIndexes: list[int] = None, targetIndex: int = None, childPosition: int = None, nameSuffix: str = None, enumerate: bool = None) -> MF.V3.Task.Task:
428    def copy_groups(self, sourceIndexes: 'list[int]' = None, targetIndex: 'int' = None, childPosition: 'int' = None, nameSuffix: 'str' = None, enumerate: 'bool' = None) -> 'Task':
429        """Copy a set of scan groups in the current open project."""
430        return Three.copy_groups(self, sourceIndexes, targetIndex, childPosition, nameSuffix, enumerate)

Copy a set of scan groups in the current open project.

def depth_map( self, camera: MF.V3.Settings.Camera.Camera = None, projector: MF.V3.Settings.Projector.Projector = None, turntable: MF.V3.Settings.Turntable.Turntable = None, capture: MF.V3.Settings.Capture.Capture = None, processing: MF.V3.Settings.Scan.Scan.Processing = None, alignWithScanner: bool = None, centerAtOrigin: bool = None) -> MF.V3.Task.Task:
432    def depth_map(self, camera: 'Camera' = None, projector: 'Projector' = None, turntable: 'Turntable' = None, capture: 'Capture' = None, processing: 'Scan.Processing' = None, alignWithScanner: 'bool' = None, centerAtOrigin: 'bool' = None) -> 'Task':
433        """Capture a depth map."""
434        return Three.depth_map(self, camera, projector, turntable, capture, processing, alignWithScanner, centerAtOrigin)

Capture a depth map.

def detect_calibration_card(self, Input: int) -> MF.V3.Task.Task:
436    def detect_calibration_card(self, Input: 'int') -> 'Task':
437        """Detect the calibration card on one or both cameras."""
438        return Three.detect_calibration_card(self, Input)

Detect the calibration card on one or both cameras.

def download_project(self, Input: int) -> MF.V3.Task.Task:
440    def download_project(self, Input: 'int') -> 'Task':
441        """Download a project from the scanner."""
442        return Three.download_project(self, Input)

Download a project from the scanner.

def export( self, selection: MF.V3.Settings.ScanSelection.ScanSelection = None, texture: bool = None, merge: bool = None, format: MF.V3.Settings.Export.Export.Format = None, scale: float = None, color: MF.V3.Settings.Export.Export.Color = None) -> MF.V3.Task.Task:
444    def export(self, selection: 'ScanSelection' = None, texture: 'bool' = None, merge: 'bool' = None, format: 'Export.Format' = None, scale: 'float' = None, color: 'Export.Color' = None) -> 'Task':
445        """Export a group of scans."""
446        return Three.export(self, selection, texture, merge, format, scale, color)

Export a group of scans.

def export_heat_map( self, selection: MF.V3.Settings.ScanSelection.ScanSelection = None, texture: bool = None, merge: bool = None, format: MF.V3.Settings.Export.Export.Format = None, scale: float = None, color: MF.V3.Settings.Export.Export.Color = None) -> MF.V3.Task.Task:
448    def export_heat_map(self, selection: 'ScanSelection' = None, texture: 'bool' = None, merge: 'bool' = None, format: 'Export.Format' = None, scale: 'float' = None, color: 'Export.Color' = None) -> 'Task':
449        """Export a mesh with vertex colors generated by the 'HeatMap' task."""
450        return Three.export_heat_map(self, selection, texture, merge, format, scale, color)

Export a mesh with vertex colors generated by the 'HeatMap' task.

def export_logs(self, Input: bool = None) -> MF.V3.Task.Task:
452    def export_logs(self, Input: 'bool' = None) -> 'Task':
453        """Export scanner logs."""
454        return Three.export_logs(self, Input)

Export scanner logs.

def export_merge( self, selection: MF.V3.Settings.ScanSelection.ScanSelection = None, texture: bool = None, merge: bool = None, format: MF.V3.Settings.Export.Export.Format = None, scale: float = None, color: MF.V3.Settings.Export.Export.Color = None) -> MF.V3.Task.Task:
456    def export_merge(self, selection: 'ScanSelection' = None, texture: 'bool' = None, merge: 'bool' = None, format: 'Export.Format' = None, scale: 'float' = None, color: 'Export.Color' = None) -> 'Task':
457        """Export a merged scan."""
458        return Three.export_merge(self, selection, texture, merge, format, scale, color)

Export a merged scan.

def flatten_group(self, Input: int) -> MF.V3.Task.Task:
460    def flatten_group(self, Input: 'int') -> 'Task':
461        """Flatten a scan group such that it only consists of single scans."""
462        return Three.flatten_group(self, Input)

Flatten a scan group such that it only consists of single scans.

def forget_wifi(self) -> MF.V3.Task.Task:
464    def forget_wifi(self) -> 'Task':
465        """Forget all wifi connections."""
466        return Three.forget_wifi(self)

Forget all wifi connections.

def has_cameras(self) -> MF.V3.Task.Task:
468    def has_cameras(self) -> 'Task':
469        """Check if the scanner has working cameras."""
470        return Three.has_cameras(self)

Check if the scanner has working cameras.

def has_projector(self) -> MF.V3.Task.Task:
472    def has_projector(self) -> 'Task':
473        """Check if the scanner has a working projector."""
474        return Three.has_projector(self)

Check if the scanner has a working projector.

def has_turntable(self) -> MF.V3.Task.Task:
476    def has_turntable(self) -> 'Task':
477        """Check if the scanner is connected to a working turntable."""
478        return Three.has_turntable(self)

Check if the scanner is connected to a working turntable.

def heat_map( self, sources: list[int] = None, targets: list[int] = None, outlierDistance: float = None) -> MF.V3.Task.Task:
480    def heat_map(self, sources: 'list[int]' = None, targets: 'list[int]' = None, outlierDistance: 'float' = None) -> 'Task':
481        """Compute the point-to-mesh distances of a source mesh to a target mesh and visualize as a heat map."""
482        return Three.heat_map(self, sources, targets, outlierDistance)

Compute the point-to-mesh distances of a source mesh to a target mesh and visualize as a heat map.

def import_file( self, name: str = None, scale: float = None, unit: MF.V3.Settings.Import.Import.Unit = None, center: bool = None, groupIndex: int = None) -> MF.V3.Task.Task:
484    def import_file(self, name: 'str' = None, scale: 'float' = None, unit: 'Import.Unit' = None, center: 'bool' = None, groupIndex: 'int' = None) -> 'Task':
485        """Import a set of 3D meshes to the current open project.  The meshes must be archived in a ZIP file."""
486        return Three.import_file(self, name, scale, unit, center, groupIndex)

Import a set of 3D meshes to the current open project. The meshes must be archived in a ZIP file.

def list_export_formats(self) -> MF.V3.Task.Task:
488    def list_export_formats(self) -> 'Task':
489        """List all export formats."""
490        return Three.list_export_formats(self)

List all export formats.

def list_groups(self) -> MF.V3.Task.Task:
492    def list_groups(self) -> 'Task':
493        """List the scan groups in the current open project."""
494        return Three.list_groups(self)

List the scan groups in the current open project.

def list_network_interfaces(self) -> MF.V3.Task.Task:
496    def list_network_interfaces(self) -> 'Task':
497        """List available wifi networks."""
498        return Three.list_network_interfaces(self)

List available wifi networks.

def list_projects(self) -> MF.V3.Task.Task:
500    def list_projects(self) -> 'Task':
501        """List all projects."""
502        return Three.list_projects(self)

List all projects.

def list_scans(self) -> MF.V3.Task.Task:
504    def list_scans(self) -> 'Task':
505        """List the scans in the current open project."""
506        return Three.list_scans(self)

List the scans in the current open project.

def list_settings(self) -> MF.V3.Task.Task:
508    def list_settings(self) -> 'Task':
509        """Get scanner settings."""
510        return Three.list_settings(self)

Get scanner settings.

def list_wifi(self) -> MF.V3.Task.Task:
512    def list_wifi(self) -> 'Task':
513        """List available wifi networks."""
514        return Three.list_wifi(self)

List available wifi networks.

def merge( self, selection: MF.V3.Settings.ScanSelection.ScanSelection = None, remesh: MF.V3.Settings.Merge.Merge.Remesh = None, simplify: MF.V3.Settings.Merge.Merge.Simplify = None, texturize: bool = None) -> MF.V3.Task.Task:
516    def merge(self, selection: 'ScanSelection' = None, remesh: 'Merge.Remesh' = None, simplify: 'Merge.Simplify' = None, texturize: 'bool' = None) -> 'Task':
517        """Merge two or more scan groups."""
518        return Three.merge(self, selection, remesh, simplify, texturize)

Merge two or more scan groups.

def merge_data( self, index: int, mergeStep: MF.V3.Settings.ScanData.ScanData.MergeStep = None, buffers: list[MF.V3.Settings.ScanData.ScanData.Buffer] = None, metadata: list[MF.V3.Settings.ScanData.ScanData.Metadata] = None) -> MF.V3.Task.Task:
520    def merge_data(self, index: 'int', mergeStep: 'ScanData.MergeStep' = None, buffers: 'list[ScanData.Buffer]' = None, metadata: 'list[ScanData.Metadata]' = None) -> 'Task':
521        """Download the raw scan data for the current merge process."""
522        return Three.merge_data(self, index, mergeStep, buffers, metadata)

Download the raw scan data for the current merge process.

def move_group(self, Input: list[int] = None) -> MF.V3.Task.Task:
524    def move_group(self, Input: 'list[int]' = None) -> 'Task':
525        """Move a scan group."""
526        return Three.move_group(self, Input)

Move a scan group.

def new_group( self, parentIndex: int = None, baseName: str = None, color: list[float] = None, visible: bool = None, collapsed: bool = None, rotation: list[float] = None, translation: list[float] = None) -> MF.V3.Task.Task:
528    def new_group(self, parentIndex: 'int' = None, baseName: 'str' = None, color: 'list[float]' = None, visible: 'bool' = None, collapsed: 'bool' = None, rotation: 'list[float]' = None, translation: 'list[float]' = None) -> 'Task':
529        """Create a new scan group."""
530        return Three.new_group(self, parentIndex, baseName, color, visible, collapsed, rotation, translation)

Create a new scan group.

def new_project(self, Input: str = None) -> MF.V3.Task.Task:
532    def new_project(self, Input: 'str' = None) -> 'Task':
533        """Create a new project."""
534        return Three.new_project(self, Input)

Create a new project.

def new_scan( self, camera: MF.V3.Settings.Camera.Camera = None, projector: MF.V3.Settings.Projector.Projector = None, turntable: MF.V3.Settings.Turntable.Turntable = None, capture: MF.V3.Settings.Capture.Capture = None, processing: MF.V3.Settings.Scan.Scan.Processing = None, alignWithScanner: bool = None, centerAtOrigin: bool = None) -> MF.V3.Task.Task:
536    def new_scan(self, camera: 'Camera' = None, projector: 'Projector' = None, turntable: 'Turntable' = None, capture: 'Capture' = None, processing: 'Scan.Processing' = None, alignWithScanner: 'bool' = None, centerAtOrigin: 'bool' = None) -> 'Task':
537        """Capture a new scan."""
538        return Three.new_scan(self, camera, projector, turntable, capture, processing, alignWithScanner, centerAtOrigin)

Capture a new scan.

def open_project(self, Input: int) -> MF.V3.Task.Task:
540    def open_project(self, Input: 'int') -> 'Task':
541        """Open an existing project."""
542        return Three.open_project(self, Input)

Open an existing project.

def pop_settings(self, Input: bool = None) -> MF.V3.Task.Task:
544    def pop_settings(self, Input: 'bool' = None) -> 'Task':
545        """Pop and restore scanner settings from the settings stack."""
546        return Three.pop_settings(self, Input)

Pop and restore scanner settings from the settings stack.

def push_settings(self) -> MF.V3.Task.Task:
548    def push_settings(self) -> 'Task':
549        """Push the current scanner settings to the settings stack."""
550        return Three.push_settings(self)

Push the current scanner settings to the settings stack.

def reboot(self) -> MF.V3.Task.Task:
552    def reboot(self) -> 'Task':
553        """Reboot the scanner."""
554        return Three.reboot(self)

Reboot the scanner.

def remove_groups(self, Input: list[int] = None) -> MF.V3.Task.Task:
556    def remove_groups(self, Input: 'list[int]' = None) -> 'Task':
557        """Remove selected scan groups."""
558        return Three.remove_groups(self, Input)

Remove selected scan groups.

def remove_projects(self, Input: list[int] = None) -> MF.V3.Task.Task:
560    def remove_projects(self, Input: 'list[int]' = None) -> 'Task':
561        """Remove selected projects."""
562        return Three.remove_projects(self, Input)

Remove selected projects.

def restore_factory_calibration(self) -> MF.V3.Task.Task:
564    def restore_factory_calibration(self) -> 'Task':
565        """Restore factory calibration."""
566        return Three.restore_factory_calibration(self)

Restore factory calibration.

def rotate_turntable(self, Input: int) -> MF.V3.Task.Task:
568    def rotate_turntable(self, Input: 'int') -> 'Task':
569        """Rotate the turntable."""
570        return Three.rotate_turntable(self, Input)

Rotate the turntable.

def scan_data( self, index: int, mergeStep: MF.V3.Settings.ScanData.ScanData.MergeStep = None, buffers: list[MF.V3.Settings.ScanData.ScanData.Buffer] = None, metadata: list[MF.V3.Settings.ScanData.ScanData.Metadata] = None) -> MF.V3.Task.Task:
572    def scan_data(self, index: 'int', mergeStep: 'ScanData.MergeStep' = None, buffers: 'list[ScanData.Buffer]' = None, metadata: 'list[ScanData.Metadata]' = None) -> 'Task':
573        """Download the raw scan data for a scan in the current open project."""
574        return Three.scan_data(self, index, mergeStep, buffers, metadata)

Download the raw scan data for a scan in the current open project.

def set_cameras( self, selection: list[int] = None, autoExposure: bool = None, exposure: int = None, analogGain: float = None, digitalGain: int = None, focus: int = None) -> MF.V3.Task.Task:
576    def set_cameras(self, selection: 'list[int]' = None, autoExposure: 'bool' = None, exposure: 'int' = None, analogGain: 'float' = None, digitalGain: 'int' = None, focus: 'int' = None) -> 'Task':
577        """Apply camera settings to one or both cameras."""
578        return Three.set_cameras(self, selection, autoExposure, exposure, analogGain, digitalGain, focus)

Apply camera settings to one or both cameras.

def set_group( self, index: int, name: str = None, color: list[float] = None, visible: bool = None, collapsed: bool = None, rotation: list[float] = None, translation: list[float] = None) -> MF.V3.Task.Task:
580    def set_group(self, index: 'int', name: 'str' = None, color: 'list[float]' = None, visible: 'bool' = None, collapsed: 'bool' = None, rotation: 'list[float]' = None, translation: 'list[float]' = None) -> 'Task':
581        """Set scan group properties."""
582        return Three.set_group(self, index, name, color, visible, collapsed, rotation, translation)

Set scan group properties.

def set_project(self, index: int = None, name: str = None) -> MF.V3.Task.Task:
584    def set_project(self, index: 'int' = None, name: 'str' = None) -> 'Task':
585        """Apply settings to the current open project."""
586        return Three.set_project(self, index, name)

Apply settings to the current open project.

def set_projector( self, on: bool = None, brightness: float = None, pattern: MF.V3.Settings.Projector.Projector.Pattern = None, image: MF.V3.Settings.Projector.Projector.Image = None, color: list[float] = None, buffer: bytes = None) -> MF.V3.Task.Task:
588    def set_projector(self, on: 'bool' = None, brightness: 'float' = None, pattern: 'Projector.Pattern' = None, image: 'Projector.Image' = None, color: 'list[float]' = None, buffer: 'bytes' = None) -> 'Task':
589        """Apply projector settings."""
590        return Three.set_projector(self, on, brightness, pattern, image, color, buffer)

Apply projector settings.

def shutdown(self) -> MF.V3.Task.Task:
592    def shutdown(self) -> 'Task':
593        """Shutdown the scanner."""
594        return Three.shutdown(self)

Shutdown the scanner.

def split_group(self, Input: int) -> MF.V3.Task.Task:
596    def split_group(self, Input: 'int') -> 'Task':
597        """Split a scan group (ie. move its subgroups to its parent group)."""
598        return Three.split_group(self, Input)

Split a scan group (ie. move its subgroups to its parent group).

def start_video(self) -> MF.V3.Task.Task:
600    def start_video(self) -> 'Task':
601        """Start the video stream."""
602        return Three.start_video(self)

Start the video stream.

def stop_video(self) -> MF.V3.Task.Task:
604    def stop_video(self) -> 'Task':
605        """Stop the video stream."""
606        return Three.stop_video(self)

Stop the video stream.

def system_info( self, updateMajor: bool = None, updateNightly: bool = None) -> MF.V3.Task.Task:
608    def system_info(self, updateMajor: 'bool' = None, updateNightly: 'bool' = None) -> 'Task':
609        """Get system information."""
610        return Three.system_info(self, updateMajor, updateNightly)

Get system information.

def transform_group( self, index: int, name: str = None, color: list[float] = None, visible: bool = None, collapsed: bool = None, rotation: list[float] = None, translation: list[float] = None) -> MF.V3.Task.Task:
612    def transform_group(self, index: 'int', name: 'str' = None, color: 'list[float]' = None, visible: 'bool' = None, collapsed: 'bool' = None, rotation: 'list[float]' = None, translation: 'list[float]' = None) -> 'Task':
613        """Apply a rigid transformation to a group."""
614        return Three.transform_group(self, index, name, color, visible, collapsed, rotation, translation)

Apply a rigid transformation to a group.

def turntable_calibration(self) -> MF.V3.Task.Task:
616    def turntable_calibration(self) -> 'Task':
617        """Get the turntable calibration descriptor."""
618        return Three.turntable_calibration(self)

Get the turntable calibration descriptor.

def update_settings( self, advanced: MF.V3.Settings.Advanced.Advanced = None, camera: MF.V3.Settings.Camera.Camera = None, capture: MF.V3.Settings.Capture.Capture = None, i18n: MF.V3.Settings.I18n.I18n = None, projector: MF.V3.Settings.Projector.Projector = None, style: MF.V3.Settings.Style.Style = None, turntable: MF.V3.Settings.Turntable.Turntable = None, tutorials: MF.V3.Settings.Tutorials.Tutorials = None, viewer: MF.V3.Settings.Viewer.Viewer = None, software: MF.V3.Settings.Software.Software = None) -> MF.V3.Task.Task:
620    def update_settings(self, advanced: 'Advanced' = None, camera: 'Camera' = None, capture: 'Capture' = None, i18n: 'I18n' = None, projector: 'Projector' = None, style: 'Style' = None, turntable: 'Turntable' = None, tutorials: 'Tutorials' = None, viewer: 'Viewer' = None, software: 'Software' = None) -> 'Task':
621        """Update scanner settings."""
622        return Three.update_settings(self, advanced, camera, capture, i18n, projector, style, turntable, tutorials, viewer, software)

Update scanner settings.

def upload_project(self, buffer: bytes) -> MF.V3.Task.Task:
624    def upload_project(self, buffer: 'bytes') -> 'Task':
625        """Upload a project to the scanner."""
626        return Three.upload_project(self, buffer)

Upload a project to the scanner.