mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-23 17:04:00 +01:00
datum rework
This commit is contained in:
parent
f6c75d9c23
commit
9e548409b0
14 changed files with 205 additions and 70 deletions
|
|
@ -1,14 +1,19 @@
|
|||
import React from 'react';
|
||||
import React, {useState} from 'react';
|
||||
import {Title} from '../Folder';
|
||||
|
||||
export class StackSection extends React.Component {
|
||||
|
||||
render() {
|
||||
const {title, children, isClosed, onTitleClick} = this.props;
|
||||
return <React.Fragment>
|
||||
<Title isClosed={isClosed} onClick={onTitleClick}>{title}</Title>
|
||||
{!isClosed && children}
|
||||
</React.Fragment>;
|
||||
export function StackSection(props) {
|
||||
let {title, initialCollapse, collapsible, children} = props;
|
||||
|
||||
if (collapsible === undefined) {
|
||||
collapsible = true;
|
||||
}
|
||||
|
||||
|
||||
const [visible, setVisible] = useState(!initialCollapse);
|
||||
|
||||
const onTitleClick = collapsible ? () => setVisible(visible => !visible) : undefined;
|
||||
|
||||
return <React.Fragment>
|
||||
<Title isClosed={!visible} onClick={onTitleClick}>{title}</Title>
|
||||
{visible && children}
|
||||
</React.Fragment>;
|
||||
}
|
||||
|
|
@ -1,19 +1,30 @@
|
|||
import React from 'react';
|
||||
import React, {useEffect, useRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default class InputControl extends React.Component {
|
||||
export default function InputControl(inprops) {
|
||||
|
||||
render() {
|
||||
let {type, inputRef, ...props} = this.props;
|
||||
|
||||
return <div className={type}>
|
||||
<input type='text' ref={inputRef} {...props} spellCheck='false' />
|
||||
let {type, inputRef, width, onWheel, ...props} = inprops;
|
||||
|
||||
const style = width&&{
|
||||
width
|
||||
}
|
||||
|
||||
const divRef = useRef()
|
||||
|
||||
useEffect(() => {
|
||||
if (onWheel && divRef.current) {
|
||||
divRef.current.addEventListener('wheel', e => e.preventDefault(), {passive:false})
|
||||
}
|
||||
}, [divRef.current])
|
||||
|
||||
return <div className={type} ref={divRef}>
|
||||
<input type='text' ref={inputRef} {...props} spellCheck='false' style={style} onWheel={onWheel}/>
|
||||
</div>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
InputControl.propTypes = {
|
||||
type: PropTypes.oneOf(['number', 'text']),
|
||||
type: PropTypes.oneOf(['number', 'text']),
|
||||
};
|
||||
|
||||
InputControl.defaultProps = {
|
||||
|
|
|
|||
|
|
@ -4,46 +4,36 @@ import InputControl from './InputControl';
|
|||
|
||||
export default function NumberControl(props) {
|
||||
|
||||
let {onChange, onFocus, value} = props;
|
||||
let {onChange, onFocus, value, width, baseStep, round, min, max, accelerator, cycle} = props;
|
||||
|
||||
const onChangeFromTarget = e => {
|
||||
onChange(e.target.value);
|
||||
};
|
||||
|
||||
const attachWheelListener = useCallback((input) => {
|
||||
if (!input) {
|
||||
return;
|
||||
const onWheel = (e) => {
|
||||
let delta = e.shiftKey ? e.deltaX : e.deltaY;
|
||||
let step = baseStep * (e.shiftKey ? accelerator : 1);
|
||||
let val = parseFloat(e.target.value);
|
||||
if (isNaN(val)) val = 0;
|
||||
val = val + (delta < 0 ? -step : step);
|
||||
if (min !== undefined && val < min) {
|
||||
val = cycle ? max : min;
|
||||
}
|
||||
const onWheel = (e) => {
|
||||
let {baseStep, round, min, max, onChange, accelerator} = props;
|
||||
let delta = e.shiftKey ? e.deltaX : e.deltaY;
|
||||
let step = baseStep * (e.shiftKey ? accelerator : 1);
|
||||
let val = parseFloat(e.target.value);
|
||||
if (isNaN(val)) val = 0;
|
||||
val = val + (delta < 0 ? -step : step);
|
||||
if (min !== undefined && val < min) {
|
||||
val = min;
|
||||
}
|
||||
if (max !== undefined && val > max) {
|
||||
val = max;
|
||||
}
|
||||
if (round !== 0) {
|
||||
val = val.toFixed(round);
|
||||
}
|
||||
input.value = val;
|
||||
onChange(val);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
input.addEventListener('wheel', onWheel, {passive: false});
|
||||
}, []);
|
||||
if (max !== undefined && val > max) {
|
||||
val = cycle ? min : max;
|
||||
}
|
||||
if (round !== 0) {
|
||||
val = val.toFixed(round);
|
||||
}
|
||||
onChange(val);
|
||||
};
|
||||
|
||||
return <InputControl type='number'
|
||||
value={value}
|
||||
onChange={onChangeFromTarget}
|
||||
onFocus={onFocus}
|
||||
inputRef={attachWheelListener}/>
|
||||
|
||||
width={width}
|
||||
onWheel={onWheel}/>
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,11 +7,15 @@ button {
|
|||
padding: 6px 10px;
|
||||
border: 0;
|
||||
background-color: @bg-color-9;
|
||||
font-weight: bold;
|
||||
//font-weight: bold;
|
||||
color: @font-color;
|
||||
white-space: nowrap;
|
||||
border-radius: 1px;
|
||||
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&.icon-button {
|
||||
display: inline-flex;
|
||||
|
||||
|
|
|
|||
|
|
@ -146,7 +146,10 @@ export function activate(ctx: CoreContext) {
|
|||
promise.then(({consumed, created}) => {
|
||||
|
||||
consumed.forEach(m => models.delete(m));
|
||||
created.forEach(m => models.add(m));
|
||||
created.forEach(m => {
|
||||
m.originatingOperation = i;
|
||||
models.add(m)
|
||||
});
|
||||
models$.next(Array.from(models).sort((m1, m2) => (m1.id||'').localeCompare(m2.id)));
|
||||
|
||||
runPromise(i + 1);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
import React from 'react';
|
||||
import {Group} from '../../wizard/components/form/Form';
|
||||
import {attachToForm, Group} from '../../wizard/components/form/Form';
|
||||
import {NumberField} from '../../wizard/components/form/Fields';
|
||||
import EntityList from '../../wizard/components/form/EntityList';
|
||||
import {StackSection} from "ui/components/controls/FormSection";
|
||||
import Button from "ui/components/controls/Button";
|
||||
import {IoAddCircleOutline, IoIosRemoveCircleOutline} from "react-icons/all";
|
||||
import ls from './CreateDatumWizard.less'
|
||||
import ComboBoxControl, {ComboBoxOption} from "ui/components/controls/ComboBoxControl";
|
||||
import NumberControl from "ui/components/controls/NumberControl";
|
||||
import produce from "immer";
|
||||
|
||||
export default function CreateDatumWizard() {
|
||||
return <Group>
|
||||
|
|
@ -9,5 +16,60 @@ export default function CreateDatumWizard() {
|
|||
<NumberField name='y' label='Y' />
|
||||
<NumberField name='z' label='Z' />
|
||||
<EntityList name='originatingFace' label='off of' entity='face' />
|
||||
<Rotations name='rotations' />
|
||||
</Group>;
|
||||
}
|
||||
|
||||
const Rotations = attachToForm(RotationsImpl);
|
||||
|
||||
function RotationsImpl({value, onChange}) {
|
||||
|
||||
value = value || [];
|
||||
|
||||
function add() {
|
||||
|
||||
onChange([
|
||||
...value,
|
||||
{
|
||||
axis: 'X',
|
||||
angle: 0
|
||||
}
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
return <StackSection title='rotations' collapsible>
|
||||
|
||||
{value.map((rot, i) => <div className={ls.rot} key={i}>
|
||||
<span className={ls.rotControls}>
|
||||
<span>axis:</span>
|
||||
<ComboBoxControl onChange={axis => {
|
||||
onChange(produce(value, value => {
|
||||
value[i].axis = axis
|
||||
}));
|
||||
}} value={rot.axis}>
|
||||
<ComboBoxOption value='X'>X</ComboBoxOption>
|
||||
<ComboBoxOption value='Y'>Y</ComboBoxOption>
|
||||
<ComboBoxOption value='Z'>Z</ComboBoxOption>
|
||||
</ComboBoxControl>
|
||||
<span>degree:</span>
|
||||
<NumberControl width={50} min={0} max={360} cycle onChange={angle => {
|
||||
onChange(produce(value, value => {
|
||||
value[i].angle = angle
|
||||
}));
|
||||
}} value={rot.angle} />
|
||||
</span>
|
||||
|
||||
|
||||
<Button onClick={() => onChange(produce(value, value => {
|
||||
value.splice(i, 1);
|
||||
}))} compact type='danger'><IoIosRemoveCircleOutline /></Button>
|
||||
</div>)}
|
||||
|
||||
<div>
|
||||
<Button onClick={add} compact><IoAddCircleOutline /> add rotation</Button>
|
||||
</div>
|
||||
|
||||
</StackSection>
|
||||
|
||||
}
|
||||
14
web/app/cad/craft/datum/create/CreateDatumWizard.less
Normal file
14
web/app/cad/craft/datum/create/CreateDatumWizard.less
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
.rot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.rotControls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
& > * {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
|
@ -16,4 +16,22 @@ export default {
|
|||
type: 'number',
|
||||
defaultValue: 0
|
||||
},
|
||||
rotations: {
|
||||
type: 'array',
|
||||
defaultValue: [],
|
||||
items: {
|
||||
type: 'object',
|
||||
schema: {
|
||||
axis: {
|
||||
type: 'string',
|
||||
enum: ['X', 'Y', 'Z']
|
||||
},
|
||||
angle: {
|
||||
type: 'number',
|
||||
min: 0,
|
||||
max: 360
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import {MDatum} from '../../../model/mdatum';
|
|||
import {roundInteractiveInput} from '../../wizard/roundUtils';
|
||||
import {DatumParamsRenderer} from '../DatumParamsRenderer';
|
||||
import {pointAsText} from 'renders';
|
||||
import {applyRotation} from "cad/craft/datum/rotate/rotateDatumOperation";
|
||||
|
||||
function updateCSys(csys, params, findFace) {
|
||||
csys.copy(CSys.ORIGIN);
|
||||
|
|
@ -17,6 +18,12 @@ function updateCSys(csys, params, findFace) {
|
|||
}
|
||||
}
|
||||
|
||||
params.rotations.forEach(r => {
|
||||
let axis = csys[r.axis.toLowerCase()];
|
||||
applyRotation(csys, csys, r.angle, axis);
|
||||
});
|
||||
|
||||
|
||||
csys.origin.x += params.x;
|
||||
csys.origin.y += params.y;
|
||||
csys.origin.z += params.z;
|
||||
|
|
@ -33,7 +40,7 @@ function create(params, {cadRegistry}) {
|
|||
}
|
||||
|
||||
function previewer(ctx, initialParams, updateParams) {
|
||||
|
||||
|
||||
let datum3D = new DatumObject3D(CSys.origin(), ctx.services.viewer);
|
||||
|
||||
datum3D.onMove = (begin, end, delta) => {
|
||||
|
|
@ -55,7 +62,7 @@ function previewer(ctx, initialParams, updateParams) {
|
|||
params.z = roundInteractiveInput(z);
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
function update(params) {
|
||||
updateCSys(datum3D.csys, params, ctx.services.cadRegistry.findFace);
|
||||
}
|
||||
|
|
@ -68,6 +75,23 @@ function previewer(ctx, initialParams, updateParams) {
|
|||
update(initialParams);
|
||||
SceneGraph.addToGroup(ctx.services.cadScene.workGroup, datum3D);
|
||||
|
||||
const modifications = ctx.craftService.modifications$.value;
|
||||
const preDrag = modifications.hints?.preDrag;
|
||||
if (preDrag) {
|
||||
let axis;
|
||||
if ('X' === preDrag.axis) {
|
||||
axis = datum3D.csysObj.xAxis;
|
||||
} else if ('Y' === preDrag.axis) {
|
||||
axis = datum3D.csysObj.yAxis;
|
||||
} else if ('Z' === preDrag.axis) {
|
||||
axis = datum3D.csysObj.zAxis;
|
||||
}
|
||||
|
||||
if (axis) {
|
||||
ctx.services.modelMouseEventSystem.dispatchMousedown(preDrag.event, [{object: axis.handle}]);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
update, dispose
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export default class CSysObject3D extends Object3D {
|
|||
this.csys = csys;
|
||||
this.sceneSetup = sceneSetup;
|
||||
|
||||
function createBasisArrow(axis, color) {
|
||||
function createBasisArrow(name, axis, color) {
|
||||
let meshArrow = new MeshArrow({
|
||||
dir: axis,
|
||||
color,
|
||||
|
|
@ -22,12 +22,13 @@ export default class CSysObject3D extends Object3D {
|
|||
materialCreate: p => new MeshLambertMaterial(p),
|
||||
...arrowParams
|
||||
});
|
||||
meshArrow.name = name;
|
||||
return meshArrow;
|
||||
}
|
||||
|
||||
this.xAxis = createBasisArrow(AXIS.X, 0xFF0000);
|
||||
this.yAxis = createBasisArrow(AXIS.Y, 0x00FF00);
|
||||
this.zAxis = createBasisArrow(AXIS.Z, 0x0000FF);
|
||||
this.xAxis = createBasisArrow('X', AXIS.X, 0xFF0000);
|
||||
this.yAxis = createBasisArrow('Y', AXIS.Y, 0x00FF00);
|
||||
this.zAxis = createBasisArrow('Z', AXIS.Z, 0x0000FF);
|
||||
|
||||
this.add(this.xAxis);
|
||||
this.add(this.yAxis);
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ function previewer(ctx, initialParams) {
|
|||
}
|
||||
}
|
||||
|
||||
function applyRotation(origCsys, csys, angle, axis) {
|
||||
export function applyRotation(origCsys, csys, angle, axis) {
|
||||
auxMatrix.rotate(angle * DEG_RAD, axis, ORIGIN);
|
||||
auxMatrix.__apply(origCsys.x, csys.x);
|
||||
auxMatrix.__apply(origCsys.y, csys.y);
|
||||
|
|
|
|||
|
|
@ -18,12 +18,8 @@ export interface SectionWidgetProps extends ContainerBasicProps {
|
|||
}
|
||||
|
||||
export function SectionWidget(props: SectionWidgetProps) {
|
||||
const [visible, setVisible] = useState(!props.initialCollapse);
|
||||
|
||||
const onTitleClick = props.collapsible ? () => setVisible(visible => !visible) : undefined;
|
||||
|
||||
return <StackSection title={props.title} onTitleClick={onTitleClick} isClosed={!visible}>
|
||||
{visible && <ContainerWidget content={props.content} />}
|
||||
return <StackSection title={props.title}>
|
||||
<ContainerWidget content={props.content} />
|
||||
</StackSection>
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export abstract class MObject {
|
|||
TYPE: EntityKind;
|
||||
|
||||
id: string;
|
||||
originatingOperation: number = -1;
|
||||
ext: any = {};
|
||||
|
||||
protected constructor(TYPE, id) {
|
||||
|
|
|
|||
|
|
@ -105,12 +105,18 @@ export default class DatumView extends View {
|
|||
this.csysObj.add(this.menuButton);
|
||||
}
|
||||
|
||||
dragStart(e, axis) {
|
||||
if (!isReadOnly() && !this.operationStarted) {
|
||||
selectDatum(datum);
|
||||
beginOperation('DATUM_MOVE');
|
||||
}
|
||||
super.dragStart(e, axis);
|
||||
dragStart(event, axis) {
|
||||
ctx.craftService.historyTravel.setPointer(datum.originatingOperation - 1, {
|
||||
preDrag: {
|
||||
event, axis: axis.name
|
||||
}
|
||||
});
|
||||
// if (!isReadOnly() && !this.operationStarted) {
|
||||
// const history = ctx.craftService.modifications$.value.history;
|
||||
// selectDatum(datum);
|
||||
// beginOperation('DATUM_MOVE');
|
||||
// }
|
||||
// super.dragStart(event, axis);
|
||||
}
|
||||
|
||||
beginOperation(freezeDragging = false) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue