Make the stream idle option a slider for time
This commit is contained in:
		@ -23,7 +23,10 @@ import {
 | 
			
		||||
import { isDesktop } from 'lib/isDesktop'
 | 
			
		||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
 | 
			
		||||
import { EngineConnectionStateType } from 'lang/std/engineConnection'
 | 
			
		||||
import useEngineStreamContext, { EngineStreamState, EngineStreamTransition } from 'hooks/useEngineStreamContext'
 | 
			
		||||
import useEngineStreamContext, {
 | 
			
		||||
  EngineStreamState,
 | 
			
		||||
  EngineStreamTransition,
 | 
			
		||||
} from 'hooks/useEngineStreamContext'
 | 
			
		||||
 | 
			
		||||
export function Toolbar({
 | 
			
		||||
  className = '',
 | 
			
		||||
 | 
			
		||||
@ -97,10 +97,12 @@ export class CameraControls {
 | 
			
		||||
  wasDragging: boolean
 | 
			
		||||
  mouseDownPosition: Vector2
 | 
			
		||||
  mouseNewPosition: Vector2
 | 
			
		||||
  old: {
 | 
			
		||||
    camera: PerspectiveCamera,
 | 
			
		||||
    target: Vector3,
 | 
			
		||||
  } | undefined
 | 
			
		||||
  old:
 | 
			
		||||
    | {
 | 
			
		||||
        camera: PerspectiveCamera
 | 
			
		||||
        target: Vector3
 | 
			
		||||
      }
 | 
			
		||||
    | undefined
 | 
			
		||||
  rotationSpeed = 0.3
 | 
			
		||||
  enableRotate = true
 | 
			
		||||
  enablePan = true
 | 
			
		||||
@ -960,33 +962,51 @@ export class CameraControls {
 | 
			
		||||
    // camera coordinates after a zoom-to-fit... So this is much easier, and
 | 
			
		||||
    // maps better to screen coordinates.
 | 
			
		||||
 | 
			
		||||
    await this.engineCommandManager.sendSceneCommand({
 | 
			
		||||
      type: 'modeling_cmd_batch_req',
 | 
			
		||||
      batch_id: uuidv4(),
 | 
			
		||||
      responses: true,
 | 
			
		||||
      requests: [
 | 
			
		||||
        {
 | 
			
		||||
          type: 'modeling_cmd_req',
 | 
			
		||||
          cmd_id: uuidv4(),
 | 
			
		||||
          cmd: {
 | 
			
		||||
            type: 'zoom_to_fit',
 | 
			
		||||
            object_ids: zoomObjectId ? [zoomObjectId] : [], // leave empty to zoom to all objects
 | 
			
		||||
            padding: panesWidth > 0 ? (window.innerWidth / panesWidth) : 0.2, // padding around the objects
 | 
			
		||||
    await this.engineCommandManager
 | 
			
		||||
      .sendSceneCommand({
 | 
			
		||||
        type: 'modeling_cmd_batch_req',
 | 
			
		||||
        batch_id: uuidv4(),
 | 
			
		||||
        responses: true,
 | 
			
		||||
        requests: [
 | 
			
		||||
          {
 | 
			
		||||
            type: 'modeling_cmd_req',
 | 
			
		||||
            cmd_id: uuidv4(),
 | 
			
		||||
            cmd: {
 | 
			
		||||
              type: 'zoom_to_fit',
 | 
			
		||||
              object_ids: zoomObjectId ? [zoomObjectId] : [], // leave empty to zoom to all objects
 | 
			
		||||
              padding: panesWidth > 0 ? window.innerWidth / panesWidth : 0.2, // padding around the objects
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          type: 'modeling_cmd_req',
 | 
			
		||||
          cmd: {
 | 
			
		||||
            type: 'camera_drag_start',
 | 
			
		||||
            interaction: 'pan',
 | 
			
		||||
            window: { x: 0, y: 0 },
 | 
			
		||||
          {
 | 
			
		||||
            type: 'modeling_cmd_req',
 | 
			
		||||
            cmd: {
 | 
			
		||||
              type: 'camera_drag_start',
 | 
			
		||||
              interaction: 'pan',
 | 
			
		||||
              window: { x: 0, y: 0 },
 | 
			
		||||
            },
 | 
			
		||||
            cmd_id: uuidv4(),
 | 
			
		||||
          },
 | 
			
		||||
          cmd_id: uuidv4(),
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          {
 | 
			
		||||
            type: 'modeling_cmd_req',
 | 
			
		||||
            cmd: {
 | 
			
		||||
              type: 'camera_drag_move',
 | 
			
		||||
              interaction: 'pan',
 | 
			
		||||
              window: {
 | 
			
		||||
                x: goRightPx,
 | 
			
		||||
                y: 0,
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
            cmd_id: uuidv4(),
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      })
 | 
			
		||||
      // engineCommandManager can't subscribe to batch responses so we'll send
 | 
			
		||||
      // this one off by its lonesome after.
 | 
			
		||||
      .then(() =>
 | 
			
		||||
        this.engineCommandManager.sendSceneCommand({
 | 
			
		||||
          type: 'modeling_cmd_req',
 | 
			
		||||
          cmd: {
 | 
			
		||||
            type: 'camera_drag_move',
 | 
			
		||||
            type: 'camera_drag_end',
 | 
			
		||||
            interaction: 'pan',
 | 
			
		||||
            window: {
 | 
			
		||||
              x: goRightPx,
 | 
			
		||||
@ -994,23 +1014,8 @@ export class CameraControls {
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
          cmd_id: uuidv4(),
 | 
			
		||||
        },
 | 
			
		||||
      ]
 | 
			
		||||
    })
 | 
			
		||||
    // engineCommandManager can't subscribe to batch responses so we'll send
 | 
			
		||||
    // this one off by its lonesome after.
 | 
			
		||||
    .then(() => this.engineCommandManager.sendSceneCommand({
 | 
			
		||||
      type: 'modeling_cmd_req',
 | 
			
		||||
      cmd: {
 | 
			
		||||
        type: 'camera_drag_end',
 | 
			
		||||
        interaction: 'pan',
 | 
			
		||||
        window: {
 | 
			
		||||
          x: goRightPx,
 | 
			
		||||
          y: 0,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      cmd_id: uuidv4(),
 | 
			
		||||
    }))
 | 
			
		||||
        })
 | 
			
		||||
      )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async tweenCameraToQuaternion(
 | 
			
		||||
 | 
			
		||||
@ -29,7 +29,9 @@ export const CommandBar = () => {
 | 
			
		||||
  }, [pathname])
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (immediateState.type !== EngineConnectionStateType.ConnectionEstablished) {
 | 
			
		||||
    if (
 | 
			
		||||
      immediateState.type !== EngineConnectionStateType.ConnectionEstablished
 | 
			
		||||
    ) {
 | 
			
		||||
      commandBarSend({ type: 'Close' })
 | 
			
		||||
    }
 | 
			
		||||
  }, [immediateState])
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,8 @@ export function CommandBarOpenButton() {
 | 
			
		||||
  const { immediateState } = useNetworkContext()
 | 
			
		||||
  const platform = usePlatform()
 | 
			
		||||
 | 
			
		||||
  const isDisabled = immediateState.type !== EngineConnectionStateType.ConnectionEstablished
 | 
			
		||||
  const isDisabled =
 | 
			
		||||
    immediateState.type !== EngineConnectionStateType.ConnectionEstablished
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <button
 | 
			
		||||
 | 
			
		||||
@ -9,13 +9,14 @@ import { btnName } from 'lib/cameraControls'
 | 
			
		||||
import { trap } from 'lib/trap'
 | 
			
		||||
import { sendSelectEventToEngine } from 'lib/selections'
 | 
			
		||||
import { kclManager, engineCommandManager } from 'lib/singletons'
 | 
			
		||||
import {
 | 
			
		||||
  EngineCommandManagerEvents,
 | 
			
		||||
} from 'lang/std/engineConnection'
 | 
			
		||||
import { EngineCommandManagerEvents } from 'lang/std/engineConnection'
 | 
			
		||||
import { useRouteLoaderData } from 'react-router-dom'
 | 
			
		||||
import { PATHS } from 'lib/paths'
 | 
			
		||||
import { IndexLoaderData } from 'lib/types'
 | 
			
		||||
import useEngineStreamContext, { EngineStreamState, EngineStreamTransition } from 'hooks/useEngineStreamContext'
 | 
			
		||||
import useEngineStreamContext, {
 | 
			
		||||
  EngineStreamState,
 | 
			
		||||
  EngineStreamTransition,
 | 
			
		||||
} from 'hooks/useEngineStreamContext'
 | 
			
		||||
 | 
			
		||||
export const EngineStream = () => {
 | 
			
		||||
  const { setAppState } = useAppState()
 | 
			
		||||
@ -46,7 +47,7 @@ export const EngineStream = () => {
 | 
			
		||||
  // so those exception people don't see.
 | 
			
		||||
  const REASONABLE_TIME_TO_REFRESH_STREAM_SIZE = 100
 | 
			
		||||
 | 
			
		||||
  const configure =  () => {
 | 
			
		||||
  const configure = () => {
 | 
			
		||||
    engineStreamActor.send({
 | 
			
		||||
      type: EngineStreamTransition.StartOrReconfigureEngine,
 | 
			
		||||
      modelingMachineActorSend,
 | 
			
		||||
@ -57,9 +58,9 @@ export const EngineStream = () => {
 | 
			
		||||
      onMediaStream(mediaStream: MediaStream) {
 | 
			
		||||
        engineStreamActor.send({
 | 
			
		||||
          type: EngineStreamTransition.SetMediaStream,
 | 
			
		||||
          mediaStream
 | 
			
		||||
          mediaStream,
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -91,11 +92,13 @@ export const EngineStream = () => {
 | 
			
		||||
      const canvas = engineStreamState.context.canvasRef?.current
 | 
			
		||||
      if (!canvas) return
 | 
			
		||||
 | 
			
		||||
      if (Math.abs(video.width - window.innerWidth) > 4 || Math.abs(video.height - window.innerHeight) > 4) {
 | 
			
		||||
      if (
 | 
			
		||||
        Math.abs(video.width - window.innerWidth) > 4 ||
 | 
			
		||||
        Math.abs(video.height - window.innerHeight) > 4
 | 
			
		||||
      ) {
 | 
			
		||||
        timeoutStart.current = Date.now()
 | 
			
		||||
        configure()
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    }, REASONABLE_TIME_TO_REFRESH_STREAM_SIZE)
 | 
			
		||||
 | 
			
		||||
    return () => {
 | 
			
		||||
@ -105,7 +108,10 @@ export const EngineStream = () => {
 | 
			
		||||
 | 
			
		||||
  // When the video and canvas element references are set, start the engine.
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (engineStreamState.context.canvasRef.current && engineStreamState.context.videoRef.current) {
 | 
			
		||||
    if (
 | 
			
		||||
      engineStreamState.context.canvasRef.current &&
 | 
			
		||||
      engineStreamState.context.videoRef.current
 | 
			
		||||
    ) {
 | 
			
		||||
      engineStreamActor.send({
 | 
			
		||||
        type: EngineStreamTransition.StartOrReconfigureEngine,
 | 
			
		||||
        modelingMachineActorSend,
 | 
			
		||||
@ -114,24 +120,30 @@ export const EngineStream = () => {
 | 
			
		||||
        onMediaStream(mediaStream: MediaStream) {
 | 
			
		||||
          engineStreamActor.send({
 | 
			
		||||
            type: EngineStreamTransition.SetMediaStream,
 | 
			
		||||
            mediaStream
 | 
			
		||||
            mediaStream,
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
        },
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }, [engineStreamState.context.canvasRef.current, engineStreamState.context.videoRef.current])
 | 
			
		||||
  }, [
 | 
			
		||||
    engineStreamState.context.canvasRef.current,
 | 
			
		||||
    engineStreamState.context.videoRef.current,
 | 
			
		||||
  ])
 | 
			
		||||
 | 
			
		||||
  // On settings change, reconfigure the engine. When paused this gets really tricky,
 | 
			
		||||
  // and also requires onMediaStream to be set!
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    engineStreamActor.send({
 | 
			
		||||
      type: EngineStreamTransition.StartOrReconfigureEngine, modelingMachineActorSend, settings: settingsEngine, setAppState,
 | 
			
		||||
        onMediaStream(mediaStream: MediaStream) {
 | 
			
		||||
          engineStreamActor.send({
 | 
			
		||||
            type: EngineStreamTransition.SetMediaStream,
 | 
			
		||||
            mediaStream
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
      type: EngineStreamTransition.StartOrReconfigureEngine,
 | 
			
		||||
      modelingMachineActorSend,
 | 
			
		||||
      settings: settingsEngine,
 | 
			
		||||
      setAppState,
 | 
			
		||||
      onMediaStream(mediaStream: MediaStream) {
 | 
			
		||||
        engineStreamActor.send({
 | 
			
		||||
          type: EngineStreamTransition.SetMediaStream,
 | 
			
		||||
          mediaStream,
 | 
			
		||||
        })
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
  }, [settings.context])
 | 
			
		||||
 | 
			
		||||
@ -147,7 +159,7 @@ export const EngineStream = () => {
 | 
			
		||||
    }
 | 
			
		||||
  }, [file?.path, engineCommandManager.engineConnection])
 | 
			
		||||
 | 
			
		||||
  const IDLE_TIME_MS = 1000 * 6
 | 
			
		||||
  const IDLE_TIME_MS = Number(streamIdleMode) * 1000 * 60
 | 
			
		||||
 | 
			
		||||
  // When streamIdleMode is changed, setup or teardown the timeouts
 | 
			
		||||
  const timeoutStart = useRef<number | null>(null)
 | 
			
		||||
@ -181,10 +193,10 @@ export const EngineStream = () => {
 | 
			
		||||
  }, [modelingMachineState])
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!streamIdleMode) return 
 | 
			
		||||
    if (!streamIdleMode) return
 | 
			
		||||
 | 
			
		||||
    const onAnyInput = () => {
 | 
			
		||||
      // Just in case it happens in the middle of the user turning off 
 | 
			
		||||
      // Just in case it happens in the middle of the user turning off
 | 
			
		||||
      // idle mode.
 | 
			
		||||
      if (!streamIdleMode) {
 | 
			
		||||
        timeoutStart.current = null
 | 
			
		||||
@ -200,9 +212,9 @@ export const EngineStream = () => {
 | 
			
		||||
          onMediaStream(mediaStream: MediaStream) {
 | 
			
		||||
            engineStreamActor.send({
 | 
			
		||||
              type: EngineStreamTransition.SetMediaStream,
 | 
			
		||||
              mediaStream
 | 
			
		||||
              mediaStream,
 | 
			
		||||
            })
 | 
			
		||||
          }
 | 
			
		||||
          },
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@ -236,7 +248,7 @@ export const EngineStream = () => {
 | 
			
		||||
    }
 | 
			
		||||
  }, [streamIdleMode, engineStreamState.value])
 | 
			
		||||
 | 
			
		||||
   const isNetworkOkay =
 | 
			
		||||
  const isNetworkOkay =
 | 
			
		||||
    overallState === NetworkHealthState.Ok ||
 | 
			
		||||
    overallState === NetworkHealthState.Weak
 | 
			
		||||
 | 
			
		||||
@ -273,12 +285,15 @@ export const EngineStream = () => {
 | 
			
		||||
      />
 | 
			
		||||
      <canvas
 | 
			
		||||
        key={engineStreamActor.id + 'canvas'}
 | 
			
		||||
      ref={engineStreamState.context.canvasRef} className="cursor-pointer" id="freeze-frame">No canvas support</canvas>
 | 
			
		||||
        ref={engineStreamState.context.canvasRef}
 | 
			
		||||
        className="cursor-pointer"
 | 
			
		||||
        id="freeze-frame"
 | 
			
		||||
      >
 | 
			
		||||
        No canvas support
 | 
			
		||||
      </canvas>
 | 
			
		||||
      <ClientSideScene
 | 
			
		||||
        cameraControls={settings.context.modeling.mouseControls.current}
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
)
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,6 @@ import { NetworkMachineIndicator } from './NetworkMachineIndicator'
 | 
			
		||||
import { ModelStateIndicator } from './ModelStateIndicator'
 | 
			
		||||
import { reportRejection } from 'lib/trap'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export function LowerRightControls({
 | 
			
		||||
  children,
 | 
			
		||||
  coreDumpManager,
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,9 @@
 | 
			
		||||
import { useEffect, useState } from 'react'
 | 
			
		||||
import { useEngineCommands } from './EngineCommands'
 | 
			
		||||
import { CustomIcon } from './CustomIcon'
 | 
			
		||||
import useEngineStreamContext, { EngineStreamState } from 'hooks/useEngineStreamContext'
 | 
			
		||||
import useEngineStreamContext, {
 | 
			
		||||
  EngineStreamState,
 | 
			
		||||
} from 'hooks/useEngineStreamContext'
 | 
			
		||||
 | 
			
		||||
export const ModelStateIndicator = () => {
 | 
			
		||||
  const [commands] = useEngineCommands()
 | 
			
		||||
@ -26,26 +28,13 @@ export const ModelStateIndicator = () => {
 | 
			
		||||
  let dataTestId = 'model-state-indicator'
 | 
			
		||||
 | 
			
		||||
  if (engineStreamState.value === EngineStreamState.Paused) {
 | 
			
		||||
    className +=
 | 
			
		||||
      'text-secondary'
 | 
			
		||||
    icon = (
 | 
			
		||||
      <CustomIcon
 | 
			
		||||
        data-testid={dataTestId + '-paused'}
 | 
			
		||||
        name="parallel"
 | 
			
		||||
      />
 | 
			
		||||
    )
 | 
			
		||||
    className += 'text-secondary'
 | 
			
		||||
    icon = <CustomIcon data-testid={dataTestId + '-paused'} name="parallel" />
 | 
			
		||||
  } else if (engineStreamState.value === EngineStreamState.Resuming) {
 | 
			
		||||
    className +=
 | 
			
		||||
      'text-secondary'
 | 
			
		||||
    icon = (
 | 
			
		||||
      <CustomIcon
 | 
			
		||||
        data-testid={dataTestId + '-resuming'}
 | 
			
		||||
        name="parallel"
 | 
			
		||||
      />
 | 
			
		||||
    )
 | 
			
		||||
    className += 'text-secondary'
 | 
			
		||||
    icon = <CustomIcon data-testid={dataTestId + '-resuming'} name="parallel" />
 | 
			
		||||
  } else if (isDone) {
 | 
			
		||||
    className +=
 | 
			
		||||
      'text-secondary'
 | 
			
		||||
    className += 'text-secondary'
 | 
			
		||||
    icon = (
 | 
			
		||||
      <CustomIcon
 | 
			
		||||
        data-testid={dataTestId + '-execution-done'}
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ import {
 | 
			
		||||
  useContext,
 | 
			
		||||
  MutableRefObject,
 | 
			
		||||
  forwardRef,
 | 
			
		||||
} from 'react'm
 | 
			
		||||
} from 'react'
 | 
			
		||||
import { useHotkeys } from 'react-hotkeys-hook'
 | 
			
		||||
import { SidebarAction, SidebarType, sidebarPanes } from './ModelingPanes'
 | 
			
		||||
import Tooltip from 'components/Tooltip'
 | 
			
		||||
@ -25,7 +25,7 @@ import { useKclContext } from 'lang/KclProvider'
 | 
			
		||||
import { MachineManagerContext } from 'components/MachineManagerProvider'
 | 
			
		||||
 | 
			
		||||
interface ModelingSidebarProps {
 | 
			
		||||
  paneOpacity: '' | 'opacity-20' | 'opacity-40',
 | 
			
		||||
  paneOpacity: '' | 'opacity-20' | 'opacity-40'
 | 
			
		||||
  ref: MutableRefObject<HTMLDivElement>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -38,7 +38,10 @@ function getPlatformString(): 'web' | 'desktop' {
 | 
			
		||||
  return isDesktop() ? 'desktop' : 'web'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const ModelingSidebar = forwardRef(function ModelingSidebar({ paneOpacity }: ModelingSidebarProps, ref) {
 | 
			
		||||
export const ModelingSidebar = forwardRef(function ModelingSidebar(
 | 
			
		||||
  { paneOpacity }: ModelingSidebarProps,
 | 
			
		||||
  ref
 | 
			
		||||
) {
 | 
			
		||||
  const machineManager = useContext(MachineManagerContext)
 | 
			
		||||
  const { commandBarSend } = useCommandsContext()
 | 
			
		||||
  const kclContext = useKclContext()
 | 
			
		||||
 | 
			
		||||
@ -22,11 +22,11 @@ export enum EngineStreamTransition {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface EngineStreamContext {
 | 
			
		||||
  pool: string | null,
 | 
			
		||||
  authToken: string | null,
 | 
			
		||||
  mediaStream: MediaStream | null,
 | 
			
		||||
  videoRef: MutableRefObject<HTMLVideoElement | null>,
 | 
			
		||||
  canvasRef: MutableRefObject<HTMLCanvasElement | null>,
 | 
			
		||||
  pool: string | null
 | 
			
		||||
  authToken: string | null
 | 
			
		||||
  mediaStream: MediaStream | null
 | 
			
		||||
  videoRef: MutableRefObject<HTMLVideoElement | null>
 | 
			
		||||
  canvasRef: MutableRefObject<HTMLCanvasElement | null>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getDimensions(streamWidth: number, streamHeight: number) {
 | 
			
		||||
@ -42,185 +42,196 @@ export function getDimensions(streamWidth: number, streamHeight: number) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const engineStreamMachine = setup({
 | 
			
		||||
    types: {
 | 
			
		||||
      context: {} as EngineStreamContext,
 | 
			
		||||
      input: {} as EngineStreamContext,
 | 
			
		||||
    },
 | 
			
		||||
    actions: {
 | 
			
		||||
      [EngineStreamTransition.Play]({ context }, params: { zoomToFit: boolean }) {
 | 
			
		||||
        const canvas = context.canvasRef.current
 | 
			
		||||
        if (!canvas) return false
 | 
			
		||||
  types: {
 | 
			
		||||
    context: {} as EngineStreamContext,
 | 
			
		||||
    input: {} as EngineStreamContext,
 | 
			
		||||
  },
 | 
			
		||||
  actions: {
 | 
			
		||||
    [EngineStreamTransition.Play]({ context }, params: { zoomToFit: boolean }) {
 | 
			
		||||
      const canvas = context.canvasRef.current
 | 
			
		||||
      if (!canvas) return false
 | 
			
		||||
 | 
			
		||||
        const video = context.videoRef.current
 | 
			
		||||
        if (!video) return false
 | 
			
		||||
      const video = context.videoRef.current
 | 
			
		||||
      if (!video) return false
 | 
			
		||||
 | 
			
		||||
        const mediaStream = context.mediaStream
 | 
			
		||||
        if (!mediaStream) return false
 | 
			
		||||
      const mediaStream = context.mediaStream
 | 
			
		||||
      if (!mediaStream) return false
 | 
			
		||||
 | 
			
		||||
        video.style.display = "block"
 | 
			
		||||
        canvas.style.display = "none"
 | 
			
		||||
      video.style.display = 'block'
 | 
			
		||||
      canvas.style.display = 'none'
 | 
			
		||||
 | 
			
		||||
        video.srcObject = mediaStream
 | 
			
		||||
        void sceneInfra.camControls.restoreCameraPosition()
 | 
			
		||||
      video.srcObject = mediaStream
 | 
			
		||||
      void sceneInfra.camControls
 | 
			
		||||
        .restoreCameraPosition()
 | 
			
		||||
        .then(() => video.play())
 | 
			
		||||
        .catch((e) => {
 | 
			
		||||
            console.warn('Video playing was prevented', e, video)
 | 
			
		||||
          console.warn('Video playing was prevented', e, video)
 | 
			
		||||
        })
 | 
			
		||||
        .then(() => kclManager.executeCode(params.zoomToFit))
 | 
			
		||||
        .catch(trap)
 | 
			
		||||
      },
 | 
			
		||||
      [EngineStreamTransition.Pause]({ context }) {
 | 
			
		||||
        const video = context.videoRef.current
 | 
			
		||||
        if (!video) return
 | 
			
		||||
    },
 | 
			
		||||
    [EngineStreamTransition.Pause]({ context }) {
 | 
			
		||||
      const video = context.videoRef.current
 | 
			
		||||
      if (!video) return
 | 
			
		||||
 | 
			
		||||
        video.pause()
 | 
			
		||||
      video.pause()
 | 
			
		||||
 | 
			
		||||
        const canvas = context.canvasRef.current
 | 
			
		||||
        if (!canvas) return
 | 
			
		||||
      const canvas = context.canvasRef.current
 | 
			
		||||
      if (!canvas) return
 | 
			
		||||
 | 
			
		||||
        canvas.width = video.videoWidth
 | 
			
		||||
        canvas.height = video.videoHeight
 | 
			
		||||
        canvas.style.width = video.videoWidth + 'px'
 | 
			
		||||
        canvas.style.height = video.videoHeight + 'px'
 | 
			
		||||
        canvas.style.display = "block"
 | 
			
		||||
      canvas.width = video.videoWidth
 | 
			
		||||
      canvas.height = video.videoHeight
 | 
			
		||||
      canvas.style.width = video.videoWidth + 'px'
 | 
			
		||||
      canvas.style.height = video.videoHeight + 'px'
 | 
			
		||||
      canvas.style.display = 'block'
 | 
			
		||||
 | 
			
		||||
        const ctx = canvas.getContext("2d")
 | 
			
		||||
        if (!ctx) return
 | 
			
		||||
      const ctx = canvas.getContext('2d')
 | 
			
		||||
      if (!ctx) return
 | 
			
		||||
 | 
			
		||||
        ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
 | 
			
		||||
      ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
 | 
			
		||||
 | 
			
		||||
        // Make sure we're on the next frame for no flickering between canvas
 | 
			
		||||
        // and the video elements.
 | 
			
		||||
        window.requestAnimationFrame(() => {
 | 
			
		||||
          video.style.display = "none"
 | 
			
		||||
      // Make sure we're on the next frame for no flickering between canvas
 | 
			
		||||
      // and the video elements.
 | 
			
		||||
      window.requestAnimationFrame(() => {
 | 
			
		||||
        video.style.display = 'none'
 | 
			
		||||
 | 
			
		||||
          // Destroy the media stream only. We will re-establish it. We could
 | 
			
		||||
          // leave everything at pausing, preventing video decoders from running
 | 
			
		||||
          // but we can do even better by significantly reducing network
 | 
			
		||||
          // cards also.
 | 
			
		||||
          context.mediaStream?.getVideoTracks()[0].stop()
 | 
			
		||||
          video.srcObject = null
 | 
			
		||||
        // Destroy the media stream only. We will re-establish it. We could
 | 
			
		||||
        // leave everything at pausing, preventing video decoders from running
 | 
			
		||||
        // but we can do even better by significantly reducing network
 | 
			
		||||
        // cards also.
 | 
			
		||||
        context.mediaStream?.getVideoTracks()[0].stop()
 | 
			
		||||
        video.srcObject = null
 | 
			
		||||
 | 
			
		||||
          sceneInfra.camControls.old = {
 | 
			
		||||
            camera: sceneInfra.camControls.camera.clone(),
 | 
			
		||||
            target: sceneInfra.camControls.target.clone()
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          engineCommandManager.tearDown({ idleMode: true })
 | 
			
		||||
        })
 | 
			
		||||
      },
 | 
			
		||||
      async [EngineStreamTransition.StartOrReconfigureEngine]({ context, event }) {
 | 
			
		||||
        if (!context.authToken) return
 | 
			
		||||
 | 
			
		||||
        const video = context.videoRef.current
 | 
			
		||||
        if (!video) return
 | 
			
		||||
 | 
			
		||||
        const { width, height } = getDimensions(
 | 
			
		||||
          window.innerWidth,
 | 
			
		||||
          window.innerHeight,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        video.width = width
 | 
			
		||||
        video.height = height
 | 
			
		||||
 | 
			
		||||
        const settingsNext = {
 | 
			
		||||
          // override the pool param (?pool=) to request a specific engine instance
 | 
			
		||||
          // from a particular pool.
 | 
			
		||||
          pool: context.pool,
 | 
			
		||||
          ...event.settings,
 | 
			
		||||
        sceneInfra.camControls.old = {
 | 
			
		||||
          camera: sceneInfra.camControls.camera.clone(),
 | 
			
		||||
          target: sceneInfra.camControls.target.clone(),
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        engineCommandManager.settings = settingsNext
 | 
			
		||||
        engineCommandManager.tearDown({ idleMode: true })
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
    async [EngineStreamTransition.StartOrReconfigureEngine]({
 | 
			
		||||
      context,
 | 
			
		||||
      event,
 | 
			
		||||
    }) {
 | 
			
		||||
      if (!context.authToken) return
 | 
			
		||||
 | 
			
		||||
        engineCommandManager.start({
 | 
			
		||||
          setMediaStream: event.onMediaStream,
 | 
			
		||||
          setIsStreamReady: (isStreamReady) => event.setAppState({ isStreamReady }),
 | 
			
		||||
          width,
 | 
			
		||||
          height,
 | 
			
		||||
          token: context.authToken,
 | 
			
		||||
          settings: settingsNext,
 | 
			
		||||
          makeDefaultPlanes: () => {
 | 
			
		||||
            return makeDefaultPlanes(kclManager.engineCommandManager)
 | 
			
		||||
          },
 | 
			
		||||
          modifyGrid: (hidden: boolean) => {
 | 
			
		||||
            return modifyGrid(kclManager.engineCommandManager, hidden)
 | 
			
		||||
          },
 | 
			
		||||
        })
 | 
			
		||||
      const video = context.videoRef.current
 | 
			
		||||
      if (!video) return
 | 
			
		||||
 | 
			
		||||
        event.modelingMachineActorSend({
 | 
			
		||||
          type: 'Set context',
 | 
			
		||||
          data: {
 | 
			
		||||
            streamDimensions: {
 | 
			
		||||
              streamWidth: width,
 | 
			
		||||
              streamHeight: height,
 | 
			
		||||
            },
 | 
			
		||||
      const { width, height } = getDimensions(
 | 
			
		||||
        window.innerWidth,
 | 
			
		||||
        window.innerHeight
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
      video.width = width
 | 
			
		||||
      video.height = height
 | 
			
		||||
 | 
			
		||||
      const settingsNext = {
 | 
			
		||||
        // override the pool param (?pool=) to request a specific engine instance
 | 
			
		||||
        // from a particular pool.
 | 
			
		||||
        pool: context.pool,
 | 
			
		||||
        ...event.settings,
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      engineCommandManager.settings = settingsNext
 | 
			
		||||
 | 
			
		||||
      engineCommandManager.start({
 | 
			
		||||
        setMediaStream: event.onMediaStream,
 | 
			
		||||
        setIsStreamReady: (isStreamReady) =>
 | 
			
		||||
          event.setAppState({ isStreamReady }),
 | 
			
		||||
        width,
 | 
			
		||||
        height,
 | 
			
		||||
        token: context.authToken,
 | 
			
		||||
        settings: settingsNext,
 | 
			
		||||
        makeDefaultPlanes: () => {
 | 
			
		||||
          return makeDefaultPlanes(kclManager.engineCommandManager)
 | 
			
		||||
        },
 | 
			
		||||
        modifyGrid: (hidden: boolean) => {
 | 
			
		||||
          return modifyGrid(kclManager.engineCommandManager, hidden)
 | 
			
		||||
        },
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      event.modelingMachineActorSend({
 | 
			
		||||
        type: 'Set context',
 | 
			
		||||
        data: {
 | 
			
		||||
          streamDimensions: {
 | 
			
		||||
            streamWidth: width,
 | 
			
		||||
            streamHeight: height,
 | 
			
		||||
          },
 | 
			
		||||
        })
 | 
			
		||||
        },
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
    async [EngineStreamTransition.Resume]({ context, event }) {
 | 
			
		||||
      // engineCommandManager.engineConnection?.reattachMediaStream()
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
}).createMachine({
 | 
			
		||||
  context: (initial) => initial.input,
 | 
			
		||||
  initial: EngineStreamState.Off,
 | 
			
		||||
  states: {
 | 
			
		||||
    [EngineStreamState.Off]: {
 | 
			
		||||
      on: {
 | 
			
		||||
        [EngineStreamTransition.StartOrReconfigureEngine]: {
 | 
			
		||||
          target: EngineStreamState.On,
 | 
			
		||||
          actions: [{ type: EngineStreamTransition.StartOrReconfigureEngine }],
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      async [EngineStreamTransition.Resume]({ context, event }) {
 | 
			
		||||
        // engineCommandManager.engineConnection?.reattachMediaStream()
 | 
			
		||||
    },
 | 
			
		||||
    [EngineStreamState.On]: {
 | 
			
		||||
      on: {
 | 
			
		||||
        [EngineStreamTransition.SetMediaStream]: {
 | 
			
		||||
          target: EngineStreamState.On,
 | 
			
		||||
          actions: [
 | 
			
		||||
            assign({ mediaStream: ({ context, event }) => event.mediaStream }),
 | 
			
		||||
          ],
 | 
			
		||||
        },
 | 
			
		||||
        [EngineStreamTransition.Play]: {
 | 
			
		||||
          target: EngineStreamState.Playing,
 | 
			
		||||
          actions: [
 | 
			
		||||
            { type: EngineStreamTransition.Play, params: { zoomToFit: true } },
 | 
			
		||||
          ],
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    }
 | 
			
		||||
  }).createMachine({
 | 
			
		||||
    context: (initial) => initial.input,
 | 
			
		||||
    initial: EngineStreamState.Off,
 | 
			
		||||
    states: {
 | 
			
		||||
      [EngineStreamState.Off]: {
 | 
			
		||||
        on: {
 | 
			
		||||
          [EngineStreamTransition.StartOrReconfigureEngine]: {
 | 
			
		||||
            target: EngineStreamState.On,
 | 
			
		||||
            actions: [{ type: EngineStreamTransition.StartOrReconfigureEngine } ]
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    [EngineStreamState.Playing]: {
 | 
			
		||||
      on: {
 | 
			
		||||
        [EngineStreamTransition.StartOrReconfigureEngine]: {
 | 
			
		||||
          target: EngineStreamState.Playing,
 | 
			
		||||
          reenter: true,
 | 
			
		||||
          actions: [{ type: EngineStreamTransition.StartOrReconfigureEngine }],
 | 
			
		||||
        },
 | 
			
		||||
        [EngineStreamTransition.Pause]: {
 | 
			
		||||
          target: EngineStreamState.Paused,
 | 
			
		||||
          actions: [{ type: EngineStreamTransition.Pause }],
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      [EngineStreamState.On]: {
 | 
			
		||||
        on: {
 | 
			
		||||
          [EngineStreamTransition.SetMediaStream]: {
 | 
			
		||||
            target: EngineStreamState.On,
 | 
			
		||||
            actions: [ assign({ mediaStream: ({ context, event }) => event.mediaStream }) ]
 | 
			
		||||
          },
 | 
			
		||||
          [EngineStreamTransition.Play]: {
 | 
			
		||||
            target: EngineStreamState.Playing,
 | 
			
		||||
            actions: [ { type: EngineStreamTransition.Play, params: { zoomToFit: true }} ]
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    [EngineStreamState.Paused]: {
 | 
			
		||||
      on: {
 | 
			
		||||
        [EngineStreamTransition.StartOrReconfigureEngine]: {
 | 
			
		||||
          target: EngineStreamState.Resuming,
 | 
			
		||||
          actions: [{ type: EngineStreamTransition.StartOrReconfigureEngine }],
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      [EngineStreamState.Playing]: {
 | 
			
		||||
        on: {
 | 
			
		||||
          [EngineStreamTransition.StartOrReconfigureEngine]: {
 | 
			
		||||
            target: EngineStreamState.Playing,
 | 
			
		||||
            reenter: true,
 | 
			
		||||
            actions: [{ type: EngineStreamTransition.StartOrReconfigureEngine } ]
 | 
			
		||||
          },
 | 
			
		||||
          [EngineStreamTransition.Pause]: {
 | 
			
		||||
            target: EngineStreamState.Paused,
 | 
			
		||||
            actions: [ { type: EngineStreamTransition.Pause } ]
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    [EngineStreamState.Resuming]: {
 | 
			
		||||
      on: {
 | 
			
		||||
        [EngineStreamTransition.SetMediaStream]: {
 | 
			
		||||
          target: EngineStreamState.Resuming,
 | 
			
		||||
          actions: [
 | 
			
		||||
            assign({ mediaStream: ({ context, event }) => event.mediaStream }),
 | 
			
		||||
          ],
 | 
			
		||||
        },
 | 
			
		||||
        [EngineStreamTransition.Play]: {
 | 
			
		||||
          target: EngineStreamState.Playing,
 | 
			
		||||
          actions: [
 | 
			
		||||
            { type: EngineStreamTransition.Play, params: { zoomToFit: false } },
 | 
			
		||||
          ],
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      [EngineStreamState.Paused]: {
 | 
			
		||||
        on: {
 | 
			
		||||
          [EngineStreamTransition.StartOrReconfigureEngine]: {
 | 
			
		||||
            target: EngineStreamState.Resuming,
 | 
			
		||||
            actions: [{ type: EngineStreamTransition.StartOrReconfigureEngine } ]
 | 
			
		||||
          },
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      [EngineStreamState.Resuming]: {
 | 
			
		||||
        on: {
 | 
			
		||||
          [EngineStreamTransition.SetMediaStream]: {
 | 
			
		||||
            target: EngineStreamState.Resuming,
 | 
			
		||||
            actions: [ assign({ mediaStream: ({ context, event }) => event.mediaStream }) ]
 | 
			
		||||
          },
 | 
			
		||||
          [EngineStreamTransition.Play]: {
 | 
			
		||||
            target: EngineStreamState.Playing,
 | 
			
		||||
            actions: [ { type: EngineStreamTransition.Play, params: { zoomToFit: false }} ]
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export default createActorContext(engineStreamMachine)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -404,7 +404,8 @@ class EngineConnection extends EventTarget {
 | 
			
		||||
        default:
 | 
			
		||||
          if (this.isConnecting()) break
 | 
			
		||||
          // Means we never could do an initial connection. Reconnect everything.
 | 
			
		||||
          if (!this.pingPongSpan.ping) this.connect({ reconnect: false }).catch(reportRejection)
 | 
			
		||||
          if (!this.pingPongSpan.ping)
 | 
			
		||||
            this.connect({ reconnect: false }).catch(reportRejection)
 | 
			
		||||
          break
 | 
			
		||||
      }
 | 
			
		||||
    }, pingIntervalMs)
 | 
			
		||||
@ -542,7 +543,6 @@ class EngineConnection extends EventTarget {
 | 
			
		||||
        type: DisconnectingType.Quit,
 | 
			
		||||
      },
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@ -1164,7 +1164,7 @@ class EngineConnection extends EventTarget {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (args.reconnect) {
 | 
			
		||||
          createWebSocketConnection()
 | 
			
		||||
        createWebSocketConnection()
 | 
			
		||||
      } else {
 | 
			
		||||
        this.onNetworkStatusReady = () => {
 | 
			
		||||
          createWebSocketConnection()
 | 
			
		||||
 | 
			
		||||
@ -181,13 +181,46 @@ export function createSettings() {
 | 
			
		||||
      /**
 | 
			
		||||
       * Stream resource saving behavior toggle
 | 
			
		||||
       */
 | 
			
		||||
      streamIdleMode: new Setting<boolean>({
 | 
			
		||||
        defaultValue: false,
 | 
			
		||||
      streamIdleMode: new Setting<number | null>({
 | 
			
		||||
        defaultValue: null,
 | 
			
		||||
        description: 'Toggle stream idling, saving bandwidth and battery',
 | 
			
		||||
        validate: (v) => typeof v === 'boolean',
 | 
			
		||||
        commandConfig: {
 | 
			
		||||
          inputType: 'boolean',
 | 
			
		||||
        },
 | 
			
		||||
        validate: (v) =>
 | 
			
		||||
          v === null ||
 | 
			
		||||
          (typeof v === 'number' &&
 | 
			
		||||
            Number(v) >= 0 &&
 | 
			
		||||
            Number(v) <= 60),
 | 
			
		||||
        Component: ({ value, updateValue }) => (
 | 
			
		||||
          <div className="flex item-center gap-4 px-2 m-0 py-0">
 | 
			
		||||
            <div className="flex flex-col">
 | 
			
		||||
              <input
 | 
			
		||||
                type="checkbox"
 | 
			
		||||
                checked={value !== null}
 | 
			
		||||
                onChange={(e) => updateValue(!e.currentTarget.checked ? null : 5)}
 | 
			
		||||
                className="block w-4 h-4"
 | 
			
		||||
              />
 | 
			
		||||
              <div></div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div className="flex flex-col grow">
 | 
			
		||||
              <input
 | 
			
		||||
                type="range"
 | 
			
		||||
                onChange={(e) =>
 | 
			
		||||
                  updateValue(parseInt(e.currentTarget.value))
 | 
			
		||||
                }
 | 
			
		||||
                disabled={value === null}
 | 
			
		||||
                value={value}
 | 
			
		||||
                min={1}
 | 
			
		||||
                max={60}
 | 
			
		||||
                step={1}
 | 
			
		||||
                className="block flex-1"
 | 
			
		||||
              />
 | 
			
		||||
              { value !== null &&
 | 
			
		||||
              <div>
 | 
			
		||||
                {value === 60 ? '1 hour' : value === 1 ? '1 minute' : value + ' minutes'}
 | 
			
		||||
              </div>
 | 
			
		||||
              }
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        ),
 | 
			
		||||
      }),
 | 
			
		||||
      onboardingStatus: new Setting<string>({
 | 
			
		||||
        defaultValue: '',
 | 
			
		||||
 | 
			
		||||
@ -121,7 +121,7 @@ pub struct AppSettings {
 | 
			
		||||
    pub dismiss_web_banner: bool,
 | 
			
		||||
    /// When the user is idle, and this is true, the stream will be torn down.
 | 
			
		||||
    #[serde(default, alias = "streamIdleMode", skip_serializing_if = "is_default")]
 | 
			
		||||
    stream_idle_mode: bool,
 | 
			
		||||
    stream_idle_mode: Option<FloatOrInt>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: When we remove backwards compatibility with the old settings file, we can remove this.
 | 
			
		||||
@ -582,7 +582,7 @@ textWrapping = true
 | 
			
		||||
                        theme_color: None,
 | 
			
		||||
                        dismiss_web_banner: false,
 | 
			
		||||
                        enable_ssao: None,
 | 
			
		||||
                        stream_idle_mode: false,
 | 
			
		||||
                        stream_idle_mode: None,
 | 
			
		||||
                    },
 | 
			
		||||
                    modeling: ModelingSettings {
 | 
			
		||||
                        base_unit: UnitLength::In,
 | 
			
		||||
@ -643,7 +643,7 @@ includeSettings = false
 | 
			
		||||
                        theme_color: None,
 | 
			
		||||
                        dismiss_web_banner: false,
 | 
			
		||||
                        enable_ssao: None,
 | 
			
		||||
                        stream_idle_mode: false,
 | 
			
		||||
                        stream_idle_mode: None,
 | 
			
		||||
                    },
 | 
			
		||||
                    modeling: ModelingSettings {
 | 
			
		||||
                        base_unit: UnitLength::Yd,
 | 
			
		||||
@ -709,7 +709,7 @@ defaultProjectName = "projects-$nnn"
 | 
			
		||||
                        theme_color: None,
 | 
			
		||||
                        dismiss_web_banner: false,
 | 
			
		||||
                        enable_ssao: None,
 | 
			
		||||
                        stream_idle_mode: false,
 | 
			
		||||
                        stream_idle_mode: None,
 | 
			
		||||
                    },
 | 
			
		||||
                    modeling: ModelingSettings {
 | 
			
		||||
                        base_unit: UnitLength::Yd,
 | 
			
		||||
@ -787,7 +787,7 @@ projectDirectory = "/Users/macinatormax/Documents/kittycad-modeling-projects""#;
 | 
			
		||||
                        theme_color: None,
 | 
			
		||||
                        dismiss_web_banner: false,
 | 
			
		||||
                        enable_ssao: None,
 | 
			
		||||
                        stream_idle_mode: false,
 | 
			
		||||
                        stream_idle_mode: None,
 | 
			
		||||
                    },
 | 
			
		||||
                    modeling: ModelingSettings {
 | 
			
		||||
                        base_unit: UnitLength::Mm,
 | 
			
		||||
 | 
			
		||||
@ -123,7 +123,7 @@ includeSettings = false
 | 
			
		||||
                        theme_color: None,
 | 
			
		||||
                        dismiss_web_banner: false,
 | 
			
		||||
                        enable_ssao: None,
 | 
			
		||||
                        stream_idle_mode: false,
 | 
			
		||||
                        stream_idle_mode: None,
 | 
			
		||||
                    },
 | 
			
		||||
                    modeling: ModelingSettings {
 | 
			
		||||
                        base_unit: UnitLength::Yd,
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user