awv3
Version:
⚡ AWV3 embedded CAD
299 lines (273 loc) • 12.3 kB
JavaScript
import * as THREE from 'three';
import { Group, Input, Label, Spacer, Button } from '../../session/elements';
import { arrayDiff } from '../../session/helpers';
import Plugin from '../../session/plugin';
import Object3 from '../../three/object3';
import Hud from '../../core/hud';
import Graphics from './graphics';
import { PositionHandle, ValueHandle, PositionValueHandle } from './handle';
const resources = ['dimension', 'fixation'].reduce(
(prev, item) => ({ ...prev, [item]: require('!!url-loader!awv3-icons/32x32/' + item + '.png') }),
{},
);
export default class Dimension extends Plugin {
constructor(
session,
{ createDimensions = true, recalc = true, handlePrototypes = [PositionHandle, ValueHandle], ...args } = {},
) {
super(session, {
type: 'Dimensions',
icon: 'dimension',
createDimensions,
handlePrototypes,
recalc,
resources,
...args,
});
this.dimensions = new Map();
//note: set manually by sketcher plugin
this.selector = undefined;
this.afterSetCallback = () => {};
this.valueHandleTexturePromise = new Promise((resolve, reject) =>
new THREE.TextureLoader().load(resources['dimension'], resolve, undefined, reject),
);
}
CreateDimensions(feature) {
this.connection.execute(
`VAR obj, set;
obj = CADH_RealToId(${feature});
set = .GetDimensionSet(obj);
set.CreateDimensions(obj);`,
);
}
onEnabled() {
// Init new heads up display, add it to the view
this.hud = new Hud(this.view);
this.view.addHud(this.hud);
this.filter = new Input(this, { name: 'Filter', value: '', hint: 'filter' });
this.addElement(this.filter);
if (!this.connection) {
console.log('dimensions called without active connection');
return;
}
const observerConfig = {
manager: this.addSubscription.bind(this),
fireOnStart: true,
unsubscribeOnUndefined: true,
};
const featureParent = (this.session.plugins[this.parent] || {}).feature;
// Watch current root for changes
this.rootUnsubscribe = this.session.observe(
state => {
let tree = state.connections[state.globals.activeConnection].tree;
return tree[tree.root].item || tree.root;
},
root => {
this.removeSubscriptions();
const tree = this.connection.tree;
const itemRef =
(tree[tree.root].instances || []).find(instance => tree[instance].link === root) || root;
// Only react to parts and assemblies
if (!tree[itemRef].class) return;
const csys = tree[itemRef].coordinateSystem.map(row => new THREE.Vector3().fromArray(row));
this.hud.scene.matrix
.makeBasis(csys[1], csys[2], csys[3])
.setPosition(csys[0])
.decompose(this.hud.scene.position, this.hud.scene.quaternion, this.hud.scene.scale);
this.hud.scene.matrixWorldNeedsUpdate = true;
// Observe features and create dimensions on them
if (featureParent) {
this.createDimensions && this.CreateDimensions(featureParent);
} else {
this.createDimensions &&
this.connection.observe(
state => state.tree[root].features,
features => features.forEach(feature => this.CreateDimensions(feature)),
observerConfig,
);
}
// Fetch expressionset
this.expressionSet = tree[root].expressionSet;
this.connection.observe(
state => state.tree[root].dimensions,
(next, prev) =>
arrayDiff(next, prev, newDims =>
newDims.forEach(id => {
const tree = this.connection.tree;
if (featureParent && featureParent !== tree[tree[id].parent].members.owner.value)
return; // skip dimensions of different features
this.connection.observe(state => state.tree[id], this.onChange, {
manager: this.addSubscription.bind(this),
onRemove: this.onRemove,
fireOnStart: true,
unsubscribeOnUndefined: true,
});
}),
),
observerConfig,
);
//update all graphics on expression set changes
//note: this is necessary to update dimension value in non-incremental sketcher mode
this.connection.observe(
state => state.tree[this.expressionSet],
() => {
for (let id of this.dimensions.keys())
this.onChange(this.connection.tree[id]);
},
observerConfig,
);
if (this.selector) {
//update visual representation of GUI element when selection changes
this.selector.element.observe(
s => s.children,
x => {
const selectedIds = this.selector.getSelectedIds();
for (let [id, { element }] of this.dimensions.entries()) {
const highlight = selectedIds.indexOf(id) !== -1;
//TODO: set some other property which affects visual style
element.visible = highlight ? true : false;
//console.log("Changed selection: " + id + " := " + highlight);
}
},
observerConfig,
);
}
},
// no manager so that this sub won't be unsubscribed by removeSubscriptions when root changes
{ ...observerConfig, manager: undefined },
);
}
onDisabled() {
// Remove and destroy heads up display
this.view.removeHud(this.hud);
this.hud.destroy();
this.rootUnsubscribe();
this.destroyElements();
}
makeDimensionElement(id) {
const isDriven = Number((this.tree[id].members.isDriven || { value: 0 }).value);
const readonly = isDriven !== 0;
const nameDescriptor = this.getMemberDescriptor(id, 'paramName');
const nameElement = this.makeInputElement(nameDescriptor, 'value', { readonly, hint: 'name' });
const descriptor = this.getDimensionDescriptor(id);
const expressionElement = this.makeInputElement(descriptor, 'expression', { readonly, hint: 'expression' });
const fixation = new Button(this, {
icon: 'fixation',
hint: 'fixation',
format: Button.Format.Toggle,
color: 'blue',
flex: 0,
value: isDriven === 0,
readonly: isDriven === -1,
});
fixation.observe(
state => state.value,
value => {
this.connection.execute(
`VAR o;
o = CADH_RealToId(${id});
o.SetIsDriven(${Number(!value)});`,
);
nameElement.readonly = !value;
expressionElement.readonly = !value;
},
);
const element = new Group(this, {
format: Group.Format.Rows,
children: [nameElement, expressionElement, fixation],
});
this.filter.observe(state => state.value, value => (element.visible = nameElement.value.includes(value)), {
fireOnStart: true,
manager: element.addSubscription.bind(element),
});
return element;
}
makeInputElement(descriptor, descriptorMember, props = {}) {
const element = new Input(this, props);
element.observe(
state => state.lastEvent,
event => {
if (event.key === 'Enter') {
descriptor[descriptorMember] = element.value;
this.afterSetCallback();
}
},
);
descriptor.observeExpression(value => (element.value = value), {
fireOnStart: true,
unsubscribeOnUndefined: true,
manager: element.addSubscription.bind(element),
});
return element;
}
getDimensionDescriptor(id) {
const tree = this.connection.tree, dimension = tree[id];
const master = tree[dimension.members.master.value]; // extrusion or constraint
const masterLinkName = master.class === 'CC_Extrusion'
? dimension.name
: master.members.userValue ? 'userValue' : 'value';
let expression = master.members[masterLinkName].expression;
// supported expression forms: "a_r( ExpressionSet.taper_angle )", "ExpressionSet.limit2", "everything + else"
const matchA_R = expression.match(/^ *a_r\((.*)\) *$/);
if (matchA_R) expression = matchA_R[1];
const matchES = expression.match(/^ *ExpressionSet\.(\w+) *$/);
if (matchES) return this.getMemberDescriptor(this.expressionSet, matchES[1], { hasA_R: Boolean(matchA_R) });
else return this.getMemberDescriptor(dimension.members.master.value, masterLinkName);
}
getMemberDescriptor(id, memberName, options = {}) {
const scope = this;
return {
get value() {
return scope.connection.tree[id] && scope.connection.tree[id].members[memberName].value;
},
set value(value) {
scope.connection.execute(
`VAR o;
o = CADH_RealToId(${id});
o.${memberName} = ${JSON.stringify(value)};`,
);
},
get expression() {
return (
scope.connection.tree[id] &&
(scope.connection.tree[id].members[memberName].expression ||
scope.connection.tree[id].members[memberName].value)
);
},
set expression(value) {
const expressionSet = scope.connection.tree[id].class === 'CC_ExpressionSet';
const flag = scope.recalc && expressionSet ? 1 : 2;
const command = `_C.CADApplication.SetExpressions(${id},["${memberName}"],[${JSON.stringify(value)}],${flag});`;
scope.connection.execute(command);
},
observeExpression(onChange, options) {
return scope.connection.observe(state => state.tree[id] && this.expression, onChange, options);
},
...options,
};
}
onChange = dimension => {
const id = dimension.id;
let element, graphics;
if (this.dimensions.has(id)) {
({ element, graphics } = this.dimensions.get(id));
} else {
element = this.makeDimensionElement(id);
this.addElement(element);
graphics = Graphics(dimension.class, this);
this.hud.scene.add(graphics);
this.dimensions.set(id, { element, graphics });
}
graphics.updateFromState(dimension);
graphics.onRender();
};
onRemove = (dimension, unsubscribe) => {
const id = dimension.id;
const { element, graphics } = this.dimensions.get(id);
this.dimensions.delete(id);
element.removeSubscriptions();
this.removeElement(element);
graphics.destroy();
this.removeSubscription(unsubscribe);
};
}