Add segment length indicators to straight segments in sketch mode (#2935)
* Rough impl of line lengths, still duplicating * Make sure the labels get cleared along with the rest of the sketch * Show current units in segment length indicators * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * Re-run CI after snapshots * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * Re-run CI * Make sure `close` segments get insert segment handles * Skip engine connection tests on Safari with a todo * Fmt --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
@ -6721,7 +6721,15 @@ ${extraLine ? 'const myVar = segLen(seg01, part001)' : ''}`
|
|||||||
})
|
})
|
||||||
|
|
||||||
test.describe('Test network and connection issues', () => {
|
test.describe('Test network and connection issues', () => {
|
||||||
test('simulate network down and network little widget', async ({ page }) => {
|
test('simulate network down and network little widget', async ({
|
||||||
|
page,
|
||||||
|
browserName,
|
||||||
|
}) => {
|
||||||
|
// TODO: Don't skip Mac for these. After `window.tearDown` is working in Safari, these should work on webkit
|
||||||
|
test.skip(
|
||||||
|
browserName === 'webkit',
|
||||||
|
'Skip on Safari until `window.tearDown` is working there'
|
||||||
|
)
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
@ -6791,7 +6799,15 @@ test.describe('Test network and connection issues', () => {
|
|||||||
await expect(page.getByText('Network Health (Connected)')).toBeVisible()
|
await expect(page.getByText('Network Health (Connected)')).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Engine disconnect & reconnect in sketch mode', async ({ page }) => {
|
test('Engine disconnect & reconnect in sketch mode', async ({
|
||||||
|
page,
|
||||||
|
browserName,
|
||||||
|
}) => {
|
||||||
|
// TODO: Don't skip Mac for these. After `window.tearDown` is working in Safari, these should work on webkit
|
||||||
|
test.skip(
|
||||||
|
browserName === 'webkit',
|
||||||
|
'Skip on Safari until `window.tearDown` is working there'
|
||||||
|
)
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
Binary file not shown.
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 35 KiB |
@ -96,6 +96,7 @@ export const ClientSideScene = ({
|
|||||||
if (!canvasRef.current) return
|
if (!canvasRef.current) return
|
||||||
const canvas = canvasRef.current
|
const canvas = canvasRef.current
|
||||||
canvas.appendChild(sceneInfra.renderer.domElement)
|
canvas.appendChild(sceneInfra.renderer.domElement)
|
||||||
|
canvas.appendChild(sceneInfra.labelRenderer.domElement)
|
||||||
sceneInfra.animate()
|
sceneInfra.animate()
|
||||||
canvas.addEventListener('mousemove', sceneInfra.onMouseMove, false)
|
canvas.addEventListener('mousemove', sceneInfra.onMouseMove, false)
|
||||||
canvas.addEventListener('mousedown', sceneInfra.onMouseDown, false)
|
canvas.addEventListener('mousedown', sceneInfra.onMouseDown, false)
|
||||||
|
@ -29,6 +29,9 @@ import {
|
|||||||
INTERSECTION_PLANE_LAYER,
|
INTERSECTION_PLANE_LAYER,
|
||||||
OnMouseEnterLeaveArgs,
|
OnMouseEnterLeaveArgs,
|
||||||
RAYCASTABLE_PLANE,
|
RAYCASTABLE_PLANE,
|
||||||
|
SEGMENT_LENGTH_LABEL,
|
||||||
|
SEGMENT_LENGTH_LABEL_OFFSET_PX,
|
||||||
|
SEGMENT_LENGTH_LABEL_TEXT,
|
||||||
SKETCH_GROUP_SEGMENTS,
|
SKETCH_GROUP_SEGMENTS,
|
||||||
SKETCH_LAYER,
|
SKETCH_LAYER,
|
||||||
X_AXIS,
|
X_AXIS,
|
||||||
@ -102,6 +105,7 @@ import {
|
|||||||
} from 'lib/rectangleTool'
|
} from 'lib/rectangleTool'
|
||||||
import { getThemeColorForThreeJs } from 'lib/theme'
|
import { getThemeColorForThreeJs } from 'lib/theme'
|
||||||
import { err, trap } from 'lib/trap'
|
import { err, trap } from 'lib/trap'
|
||||||
|
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
|
||||||
|
|
||||||
type DraftSegment = 'line' | 'tangentialArcTo'
|
type DraftSegment = 'line' | 'tangentialArcTo'
|
||||||
|
|
||||||
@ -414,7 +418,7 @@ export class SceneEntities {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
let seg
|
let seg: Group
|
||||||
const _node1 = getNodeFromPath<CallExpression>(
|
const _node1 = getNodeFromPath<CallExpression>(
|
||||||
maybeModdedAst,
|
maybeModdedAst,
|
||||||
segPathToNode,
|
segPathToNode,
|
||||||
@ -1302,6 +1306,7 @@ export class SceneEntities {
|
|||||||
shape.moveTo(0, (-SEGMENT_WIDTH_PX / 2) * scale) // The width of the line in px (2.4px in this case)
|
shape.moveTo(0, (-SEGMENT_WIDTH_PX / 2) * scale) // The width of the line in px (2.4px in this case)
|
||||||
shape.lineTo(0, (SEGMENT_WIDTH_PX / 2) * scale)
|
shape.lineTo(0, (SEGMENT_WIDTH_PX / 2) * scale)
|
||||||
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
|
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
|
||||||
|
const labelGroup = group.getObjectByName(SEGMENT_LENGTH_LABEL) as Group
|
||||||
|
|
||||||
const length = Math.sqrt(
|
const length = Math.sqrt(
|
||||||
Math.pow(to[0] - from[0], 2) + Math.pow(to[1] - from[1], 2)
|
Math.pow(to[0] - from[0], 2) + Math.pow(to[1] - from[1], 2)
|
||||||
@ -1347,6 +1352,29 @@ export class SceneEntities {
|
|||||||
extraSegmentGroup.visible = isHandlesVisible
|
extraSegmentGroup.visible = isHandlesVisible
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (labelGroup) {
|
||||||
|
const labelWrapper = labelGroup.getObjectByName(
|
||||||
|
SEGMENT_LENGTH_LABEL_TEXT
|
||||||
|
) as CSS2DObject
|
||||||
|
const labelWrapperElem = labelWrapper.element as HTMLDivElement
|
||||||
|
const label = labelWrapperElem.children[0] as HTMLParagraphElement
|
||||||
|
label.innerText = `${roundOff(length)}${sceneInfra._baseUnit}`
|
||||||
|
label.classList.add(SEGMENT_LENGTH_LABEL_TEXT)
|
||||||
|
const offsetFromMidpoint = new Vector2(to[0] - from[0], to[1] - from[1])
|
||||||
|
.normalize()
|
||||||
|
.rotateAround(new Vector2(0, 0), Math.PI / 2)
|
||||||
|
.multiplyScalar(SEGMENT_LENGTH_LABEL_OFFSET_PX * scale)
|
||||||
|
label.style.setProperty('--x', `${offsetFromMidpoint.x}px`)
|
||||||
|
label.style.setProperty('--y', `${offsetFromMidpoint.y}px`)
|
||||||
|
labelWrapper.position.set(
|
||||||
|
(from[0] + to[0]) / 2 + offsetFromMidpoint.x,
|
||||||
|
(from[1] + to[1]) / 2 + offsetFromMidpoint.y,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
labelGroup.visible = isHandlesVisible
|
||||||
|
}
|
||||||
|
|
||||||
const straightSegmentBody = group.children.find(
|
const straightSegmentBody = group.children.find(
|
||||||
(child) => child.userData.type === STRAIGHT_SEGMENT_BODY
|
(child) => child.userData.type === STRAIGHT_SEGMENT_BODY
|
||||||
) as Mesh
|
) as Mesh
|
||||||
@ -1397,6 +1425,14 @@ export class SceneEntities {
|
|||||||
)
|
)
|
||||||
let shouldResolve = false
|
let shouldResolve = false
|
||||||
if (sketchSegments) {
|
if (sketchSegments) {
|
||||||
|
// We have to manually remove the CSS2DObjects
|
||||||
|
// as they don't get removed when the group is removed
|
||||||
|
sketchSegments.traverse((object) => {
|
||||||
|
if (object instanceof CSS2DObject) {
|
||||||
|
object.element.remove()
|
||||||
|
object.remove()
|
||||||
|
}
|
||||||
|
})
|
||||||
this.scene.remove(sketchSegments)
|
this.scene.remove(sketchSegments)
|
||||||
shouldResolve = true
|
shouldResolve = true
|
||||||
} else {
|
} else {
|
||||||
|
@ -31,6 +31,7 @@ import { EngineCommandManager } from 'lang/std/engineConnection'
|
|||||||
import { MouseState, SegmentOverlayPayload } from 'machines/modelingMachine'
|
import { MouseState, SegmentOverlayPayload } from 'machines/modelingMachine'
|
||||||
import { getAngle, throttle } from 'lib/utils'
|
import { getAngle, throttle } from 'lib/utils'
|
||||||
import { Themes } from 'lib/theme'
|
import { Themes } from 'lib/theme'
|
||||||
|
import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer'
|
||||||
|
|
||||||
type SendType = ReturnType<typeof useModelingContext>['send']
|
type SendType = ReturnType<typeof useModelingContext>['send']
|
||||||
|
|
||||||
@ -54,6 +55,9 @@ export const Y_AXIS = 'yAxis'
|
|||||||
export const AXIS_GROUP = 'axisGroup'
|
export const AXIS_GROUP = 'axisGroup'
|
||||||
export const SKETCH_GROUP_SEGMENTS = 'sketch-group-segments'
|
export const SKETCH_GROUP_SEGMENTS = 'sketch-group-segments'
|
||||||
export const ARROWHEAD = 'arrowhead'
|
export const ARROWHEAD = 'arrowhead'
|
||||||
|
export const SEGMENT_LENGTH_LABEL = 'segment-length-label'
|
||||||
|
export const SEGMENT_LENGTH_LABEL_TEXT = 'segment-length-label-text'
|
||||||
|
export const SEGMENT_LENGTH_LABEL_OFFSET_PX = 30
|
||||||
|
|
||||||
export interface OnMouseEnterLeaveArgs {
|
export interface OnMouseEnterLeaveArgs {
|
||||||
selected: Object3D<Object3DEventMap>
|
selected: Object3D<Object3DEventMap>
|
||||||
@ -95,6 +99,7 @@ export class SceneInfra {
|
|||||||
static instance: SceneInfra
|
static instance: SceneInfra
|
||||||
scene: Scene
|
scene: Scene
|
||||||
renderer: WebGLRenderer
|
renderer: WebGLRenderer
|
||||||
|
labelRenderer: CSS2DRenderer
|
||||||
camControls: CameraControls
|
camControls: CameraControls
|
||||||
isPerspective = true
|
isPerspective = true
|
||||||
fov = 45
|
fov = 45
|
||||||
@ -264,6 +269,13 @@ export class SceneInfra {
|
|||||||
this.renderer = new WebGLRenderer({ antialias: true, alpha: true }) // Enable transparency
|
this.renderer = new WebGLRenderer({ antialias: true, alpha: true }) // Enable transparency
|
||||||
this.renderer.setSize(window.innerWidth, window.innerHeight)
|
this.renderer.setSize(window.innerWidth, window.innerHeight)
|
||||||
this.renderer.setClearColor(0x000000, 0) // Set clear color to black with 0 alpha (fully transparent)
|
this.renderer.setClearColor(0x000000, 0) // Set clear color to black with 0 alpha (fully transparent)
|
||||||
|
|
||||||
|
// LABEL RENDERER
|
||||||
|
this.labelRenderer = new CSS2DRenderer()
|
||||||
|
this.labelRenderer.setSize(window.innerWidth, window.innerHeight)
|
||||||
|
this.labelRenderer.domElement.style.position = 'absolute'
|
||||||
|
this.labelRenderer.domElement.style.top = '0px'
|
||||||
|
this.labelRenderer.domElement.style.pointerEvents = 'none'
|
||||||
window.addEventListener('resize', this.onWindowResize)
|
window.addEventListener('resize', this.onWindowResize)
|
||||||
|
|
||||||
this.camControls = new CameraControls(
|
this.camControls = new CameraControls(
|
||||||
@ -328,6 +340,7 @@ export class SceneInfra {
|
|||||||
|
|
||||||
onWindowResize = () => {
|
onWindowResize = () => {
|
||||||
this.renderer.setSize(window.innerWidth, window.innerHeight)
|
this.renderer.setSize(window.innerWidth, window.innerHeight)
|
||||||
|
this.labelRenderer.setSize(window.innerWidth, window.innerHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
animate = () => {
|
animate = () => {
|
||||||
@ -337,6 +350,7 @@ export class SceneInfra {
|
|||||||
// console.log('animation frame', this.cameraControls.camera)
|
// console.log('animation frame', this.cameraControls.camera)
|
||||||
this.camControls.update()
|
this.camControls.update()
|
||||||
this.renderer.render(this.scene, this.camControls.camera)
|
this.renderer.render(this.scene, this.camControls.camera)
|
||||||
|
this.labelRenderer.render(this.scene, this.camControls.camera)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
Vector3,
|
Vector3,
|
||||||
} from 'three'
|
} from 'three'
|
||||||
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
|
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
|
||||||
|
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
|
||||||
import { PathToNode, SketchGroup, getTangentialArcToInfo } from 'lang/wasm'
|
import { PathToNode, SketchGroup, getTangentialArcToInfo } from 'lang/wasm'
|
||||||
import {
|
import {
|
||||||
EXTRA_SEGMENT_HANDLE,
|
EXTRA_SEGMENT_HANDLE,
|
||||||
@ -36,8 +37,14 @@ import {
|
|||||||
TANGENTIAL_ARC_TO__SEGMENT_DASH,
|
TANGENTIAL_ARC_TO__SEGMENT_DASH,
|
||||||
} from './sceneEntities'
|
} from './sceneEntities'
|
||||||
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
|
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
|
||||||
import { ARROWHEAD } from './sceneInfra'
|
import {
|
||||||
|
ARROWHEAD,
|
||||||
|
SEGMENT_LENGTH_LABEL,
|
||||||
|
SEGMENT_LENGTH_LABEL_OFFSET_PX,
|
||||||
|
SEGMENT_LENGTH_LABEL_TEXT,
|
||||||
|
} from './sceneInfra'
|
||||||
import { Themes, getThemeColorForThreeJs } from 'lib/theme'
|
import { Themes, getThemeColorForThreeJs } from 'lib/theme'
|
||||||
|
import { roundOff } from 'lib/utils'
|
||||||
|
|
||||||
export function profileStart({
|
export function profileStart({
|
||||||
from,
|
from,
|
||||||
@ -101,7 +108,7 @@ export function straightSegment({
|
|||||||
theme: Themes
|
theme: Themes
|
||||||
isSelected?: boolean
|
isSelected?: boolean
|
||||||
}): Group {
|
}): Group {
|
||||||
const group = new Group()
|
const segmentGroup = new Group()
|
||||||
|
|
||||||
const shape = new Shape()
|
const shape = new Shape()
|
||||||
shape.moveTo(0, (-SEGMENT_WIDTH_PX / 2) * scale)
|
shape.moveTo(0, (-SEGMENT_WIDTH_PX / 2) * scale)
|
||||||
@ -133,7 +140,7 @@ export function straightSegment({
|
|||||||
: STRAIGHT_SEGMENT_BODY
|
: STRAIGHT_SEGMENT_BODY
|
||||||
mesh.name = STRAIGHT_SEGMENT_BODY
|
mesh.name = STRAIGHT_SEGMENT_BODY
|
||||||
|
|
||||||
group.userData = {
|
segmentGroup.userData = {
|
||||||
type: STRAIGHT_SEGMENT,
|
type: STRAIGHT_SEGMENT,
|
||||||
id,
|
id,
|
||||||
from,
|
from,
|
||||||
@ -143,37 +150,60 @@ export function straightSegment({
|
|||||||
callExpName,
|
callExpName,
|
||||||
baseColor,
|
baseColor,
|
||||||
}
|
}
|
||||||
group.name = STRAIGHT_SEGMENT
|
segmentGroup.name = STRAIGHT_SEGMENT
|
||||||
|
segmentGroup.add(mesh)
|
||||||
|
|
||||||
const length = Math.sqrt(
|
const length = Math.sqrt(
|
||||||
Math.pow(to[0] - from[0], 2) + Math.pow(to[1] - from[1], 2)
|
Math.pow(to[0] - from[0], 2) + Math.pow(to[1] - from[1], 2)
|
||||||
)
|
)
|
||||||
const arrowGroup = createArrowhead(scale, theme, color)
|
|
||||||
arrowGroup.position.set(to[0], to[1], 0)
|
|
||||||
const dir = new Vector3()
|
|
||||||
.subVectors(new Vector3(to[0], to[1], 0), new Vector3(from[0], from[1], 0))
|
|
||||||
.normalize()
|
|
||||||
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
|
|
||||||
const pxLength = length / scale
|
const pxLength = length / scale
|
||||||
const shouldHide = pxLength < HIDE_SEGMENT_LENGTH
|
const shouldHide = pxLength < HIDE_SEGMENT_LENGTH
|
||||||
arrowGroup.visible = !shouldHide
|
|
||||||
|
|
||||||
group.add(mesh)
|
|
||||||
if (callExpName !== 'close') group.add(arrowGroup)
|
|
||||||
|
|
||||||
|
// All segment types get an extra segment handle,
|
||||||
|
// Which is a little plus sign that appears at the origin of the segment
|
||||||
|
// and can be dragged to insert a new segment
|
||||||
const extraSegmentGroup = createExtraSegmentHandle(scale, texture, theme)
|
const extraSegmentGroup = createExtraSegmentHandle(scale, texture, theme)
|
||||||
const offsetFromBase = new Vector2(to[0] - from[0], to[1] - from[1])
|
const directionVector = new Vector2(
|
||||||
.normalize()
|
to[0] - from[0],
|
||||||
.multiplyScalar(EXTRA_SEGMENT_OFFSET_PX * scale)
|
to[1] - from[1]
|
||||||
|
).normalize()
|
||||||
|
const offsetFromBase = directionVector.multiplyScalar(
|
||||||
|
EXTRA_SEGMENT_OFFSET_PX * scale
|
||||||
|
)
|
||||||
extraSegmentGroup.position.set(
|
extraSegmentGroup.position.set(
|
||||||
from[0] + offsetFromBase.x,
|
from[0] + offsetFromBase.x,
|
||||||
from[1] + offsetFromBase.y,
|
from[1] + offsetFromBase.y,
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
extraSegmentGroup.visible = !shouldHide
|
extraSegmentGroup.visible = !shouldHide
|
||||||
group.add(extraSegmentGroup)
|
segmentGroup.add(extraSegmentGroup)
|
||||||
|
|
||||||
return group
|
// Segment decorators that only apply to non-close segments
|
||||||
|
if (callExpName !== 'close') {
|
||||||
|
// an arrowhead that appears at the end of the segment
|
||||||
|
const arrowGroup = createArrowhead(scale, theme, color)
|
||||||
|
arrowGroup.position.set(to[0], to[1], 0)
|
||||||
|
const dir = new Vector3()
|
||||||
|
.subVectors(
|
||||||
|
new Vector3(to[0], to[1], 0),
|
||||||
|
new Vector3(from[0], from[1], 0)
|
||||||
|
)
|
||||||
|
.normalize()
|
||||||
|
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
|
||||||
|
arrowGroup.visible = !shouldHide
|
||||||
|
segmentGroup.add(arrowGroup)
|
||||||
|
|
||||||
|
// A length indicator that appears at the midpoint of the segment
|
||||||
|
const lengthIndicatorGroup = createLengthIndicator({
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
scale,
|
||||||
|
length,
|
||||||
|
})
|
||||||
|
segmentGroup.add(lengthIndicatorGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
return segmentGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
function createArrowhead(scale = 1, theme: Themes, color?: number): Group {
|
function createArrowhead(scale = 1, theme: Themes, color?: number): Group {
|
||||||
@ -230,6 +260,46 @@ function createExtraSegmentHandle(
|
|||||||
return extraSegmentGroup
|
return extraSegmentGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a group containing a CSS2DObject with the length of the segment
|
||||||
|
*/
|
||||||
|
function createLengthIndicator({
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
scale,
|
||||||
|
length,
|
||||||
|
}: {
|
||||||
|
from: Coords2d
|
||||||
|
to: Coords2d
|
||||||
|
scale: number
|
||||||
|
length: number
|
||||||
|
}) {
|
||||||
|
const lengthIndicatorGroup = new Group()
|
||||||
|
lengthIndicatorGroup.name = SEGMENT_LENGTH_LABEL
|
||||||
|
|
||||||
|
// Make the elements
|
||||||
|
const lengthIndicatorText = document.createElement('p')
|
||||||
|
lengthIndicatorText.classList.add(SEGMENT_LENGTH_LABEL_TEXT)
|
||||||
|
lengthIndicatorText.innerText = roundOff(length).toString()
|
||||||
|
const lengthIndicatorWrapper = document.createElement('div')
|
||||||
|
|
||||||
|
// Style the elements
|
||||||
|
lengthIndicatorWrapper.style.position = 'absolute'
|
||||||
|
lengthIndicatorWrapper.appendChild(lengthIndicatorText)
|
||||||
|
const cssObject = new CSS2DObject(lengthIndicatorWrapper)
|
||||||
|
cssObject.name = SEGMENT_LENGTH_LABEL_TEXT
|
||||||
|
|
||||||
|
// Position the elements based on the line's heading
|
||||||
|
const offsetFromMidpoint = new Vector2(to[0] - from[0], to[1] - from[1])
|
||||||
|
.normalize()
|
||||||
|
.rotateAround(new Vector2(0, 0), -Math.PI / 2)
|
||||||
|
.multiplyScalar(SEGMENT_LENGTH_LABEL_OFFSET_PX * scale)
|
||||||
|
lengthIndicatorText.style.setProperty('--x', `${offsetFromMidpoint.x}px`)
|
||||||
|
lengthIndicatorText.style.setProperty('--y', `${offsetFromMidpoint.y}px`)
|
||||||
|
lengthIndicatorGroup.add(cssObject)
|
||||||
|
return lengthIndicatorGroup
|
||||||
|
}
|
||||||
|
|
||||||
export function tangentialArcToSegment({
|
export function tangentialArcToSegment({
|
||||||
prevSegment,
|
prevSegment,
|
||||||
from,
|
from,
|
||||||
|
@ -254,6 +254,10 @@ code {
|
|||||||
color: rgb(120, 120, 120, 0.8) !important;
|
color: rgb(120, 120, 120, 0.8) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.segment-length-label-text {
|
||||||
|
transform: translate(var(--x, 0), var(--y, 0));
|
||||||
|
}
|
||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
kbd.hotkey {
|
kbd.hotkey {
|
||||||
@apply font-mono text-xs inline-block px-1 py-0.5 rounded-sm;
|
@apply font-mono text-xs inline-block px-1 py-0.5 rounded-sm;
|
||||||
|
Reference in New Issue
Block a user