awv3
Version:
⚡ AWV3 embedded CAD
304 lines (277 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 => {
console.log(this.tree[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],
isRef = dimension.class.endsWith('RefDimension')
const master = isRef
? dimension
: 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(master.id, 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)
}
}