diff --git a/modules/ui/components/Status.tsx b/modules/ui/components/Status.tsx new file mode 100644 index 00000000..3e7fafb9 --- /dev/null +++ b/modules/ui/components/Status.tsx @@ -0,0 +1,23 @@ +import * as React from "react"; +import {BsCheckCircle, BsQuestionCircle} from "react-icons/bs"; +import {FaRegTimesCircle} from "react-icons/fa"; + +export function Status({success}: { + success?: boolean +}) { + + if (success === true) { + return success + } else if (success === false) { + return fail + } else { + return unknown + } + +} \ No newline at end of file diff --git a/modules/ui/styles/global/main.less b/modules/ui/styles/global/main.less index d46cee9d..ecd3781a 100644 --- a/modules/ui/styles/global/main.less +++ b/modules/ui/styles/global/main.less @@ -29,3 +29,11 @@ pre { path { stroke: currentColor; } + +.error-text { + color: @on-color-highlight-variant-red; +} + +.warning-text { + color: @on-color-highlight-variant-yellow; +} \ No newline at end of file diff --git a/web/app/cad/assembly/assembly.ts b/web/app/cad/assembly/assembly.ts index 8140e211..5dfd2def 100644 --- a/web/app/cad/assembly/assembly.ts +++ b/web/app/cad/assembly/assembly.ts @@ -1,10 +1,8 @@ import {MObject} from "../model/mobject"; import {Param} from "../../sketcher/shapes/param"; -import Vector from "math/vector"; -import {Matrix3} from "math/l3space"; import {ISolveStage, SolvableObject} from "../../sketcher/constr/solvableObject"; import {AlgNumConstraint} from "../../sketcher/constr/ANConstraints"; -import {Constraints3D} from "./constraints3d"; +import {AssemblyCSysNode} from "./nodes/assemblyCSysNode"; export abstract class AssemblyNode implements SolvableObject { @@ -41,105 +39,3 @@ export abstract class AssemblyNode implements SolvableObject { } -export class AssemblyUnitVectorNode extends AssemblyNode { - - x = new Param(0, 'X'); - y = new Param(0, 'Y'); - z = new Param(0, 'Z'); - getVector: () => Vector; - - constructor(model: MObject, getVector: () => Vector) { - super(model); - this.getVector = getVector; - } - - visitParams(cb) { - cb(this.x); - cb(this.y); - cb(this.z); - } - - reset() { - const {x, y, z} = this.getVector(); - this.x.set(x); - this.y.set(y); - this.z.set(z); - } - - createConsistencyConstraints() { - return [ - new AlgNumConstraint(Constraints3D.UnitVectorConsistency, [this]) - ]; - } - - createRigidBodyLink(body: AssemblyCSysNode) { - return [ - new AlgNumConstraint(Constraints3D.RigidBodyLink3x3, [body, this]) - ]; - } - -} - -export class AssemblyCSysNode extends AssemblyNode { - - ox = new Param(0, 'X'); - oy = new Param(0, 'Y'); - oz = new Param(0, 'Z'); - ix = new Param(1, 'X'); - iy = new Param(0, 'Y'); - iz = new Param(0, 'Z'); - jx = new Param(0, 'X'); - jy = new Param(1, 'Y'); - jz = new Param(0, 'Z'); - kx = new Param(0, 'X'); - ky = new Param(0, 'Y'); - kz = new Param(1, 'Z'); - getTransformation: () => Matrix3; - - constructor(model: MObject, getTransformation: () => Matrix3) { - super(model); - this.getTransformation = getTransformation; - } - - visitParams(cb) { - cb(this.ox); - cb(this.oy); - cb(this.oz); - cb(this.ix); - cb(this.iy); - cb(this.iz); - cb(this.jx); - cb(this.jy); - cb(this.jz); - cb(this.kx); - cb(this.ky); - cb(this.kz); - } - - reset() { - const mx = this.getTransformation(); - this.ox.set(mx.tx); - this.oy.set(mx.ty); - this.oz.set(mx.tz); - - this.ix.set(mx.mxx); - this.iy.set(mx.myx); - this.iz.set(mx.mzx); - - this.jx.set(mx.mxy); - this.jy.set(mx.myy); - this.jz.set(mx.mzy); - - this.kx.set(mx.mxz); - this.ky.set(mx.myz); - this.kz.set(mx.mzz); - - } - - createConsistencyConstraints() { - return [ - new AlgNumConstraint(Constraints3D.CSysConsistency, [this]) - ]; - } - -} \ No newline at end of file diff --git a/web/app/cad/assembly/assemblyConstraintDefinition.ts b/web/app/cad/assembly/assemblyConstraintDefinition.ts new file mode 100644 index 00000000..49b29c88 --- /dev/null +++ b/web/app/cad/assembly/assemblyConstraintDefinition.ts @@ -0,0 +1,10 @@ +import {ConstantsDefinitions} from "../../sketcher/constr/ANConstraints"; + +export interface AssemblyConstraintDefinition { + + typeId: string; + + objects: string[]; + + constants: ConstantsDefinitions +} \ No newline at end of file diff --git a/web/app/cad/assembly/assemblyPlugin.ts b/web/app/cad/assembly/assemblyPlugin.ts new file mode 100644 index 00000000..7ecc926e --- /dev/null +++ b/web/app/cad/assembly/assemblyPlugin.ts @@ -0,0 +1,118 @@ +import {ApplicationContext} from "context"; +import {ModellerContextualActions} from "./ui/ModellerContextualActions"; +import {state, StateStream} from "lstream"; +import {AssemblyConstraintDefinition} from "./assemblyConstraintDefinition"; +import {solveAssembly as solveAssemblyImpl} from "./assemblySolver"; +import {Constraints3D, createAssemblyConstraint} from "./constraints3d"; +import {SolveStatus} from "../../sketcher/constr/AlgNumSystem"; +import {ConstantsDefinitions} from "../../sketcher/constr/ANConstraints"; +import {AssemblyView} from "./ui/AssemblyView"; +import {IoMdConstruct} from "react-icons/io"; + +export function activate(ctx: ApplicationContext) { + + const constraints$ = state([]); + const status$ = state(null); + + function getConstraints(): AssemblyConstraintDefinition[][] { + return constraints$.value; + } + + function loadConstraints(inData: AssemblyConstraintDefinition[][]): void { + constraints$.next(inData); + } + + function addConstraint(typeId: string, objects: string[], constants?: ConstantsDefinitions): void { + constraints$.mutate(stages => { + if (stages.length === 0) { + stages.push([]) + } + stages[stages.length - 1].push({ + typeId, objects, constants + }); + }) + } + + function removeConstraint(constr: AssemblyConstraintDefinition) { + constraints$.mutate(stages => { + for (let constrs of stages) { + const index = constrs.indexOf(constr); + if (index !== -1) { + constrs.splice(index, 1); + } + } + }) + } + + function solveAssembly(): void { + if (ctx.craftService.isEditingHistory()) { + console.log('skipping assembly resolve request in the history mode'); + return; + } + + const stages = constraints$.value.map(stage => stage.map(constr => { + const schema = Constraints3D[constr.typeId]; + if (!schema) { + console.error('reference to nonexistent constraint ' + constr.typeId); + return null; + } + const objects = []; + for (const id of constr.objects) { + const modelObject = ctx.cadRegistry.find(id); + if (!modelObject) { + console.warn('skipping constraint referring to nonexistent object ' + id); + return null; + } + objects.push(modelObject); + } + return createAssemblyConstraint(schema, objects) + } ).filter(x => x) ); + + const solveStatus = solveAssemblyImpl(stages); + + status$.next(solveStatus); + } + + constraints$.attach(solveAssembly); + + ctx.domService.contributeComponent(ModellerContextualActions); + + ctx.services.ui.registerFloatView('assembly', AssemblyView, 'Assembly', IoMdConstruct); + + ctx.craftService.modifications$.attach((modifications) => { + //if we reach the end reevaluate locations + if (modifications.pointer === modifications.history.length - 1) { + solveAssembly(); + } + }); + + ctx.assemblyService = { + constraints$, getConstraints, loadConstraints, solveAssembly, addConstraint, removeConstraint, status$ + } +} + +export interface AssemblyService { + + constraints$: StateStream; + + status$: StateStream; + + addConstraint(typeId: string, objects: string[], constants?: ConstantsDefinitions): void; + + removeConstraint(constr: AssemblyConstraintDefinition): void; + + solveAssembly(): void; + + loadConstraints(constraints: AssemblyConstraintDefinition[][]); + + getConstraints(): AssemblyConstraintDefinition[][]; + +} + +declare module 'context' { + interface ApplicationContext { + + assemblyService: AssemblyService; + } +} + diff --git a/web/app/cad/assembly/assemblySolver.ts b/web/app/cad/assembly/assemblySolver.ts index aa0b40e5..c80b116a 100644 --- a/web/app/cad/assembly/assemblySolver.ts +++ b/web/app/cad/assembly/assemblySolver.ts @@ -1,17 +1,18 @@ import {AlgNumConstraint} from "../../sketcher/constr/ANConstraints"; -import {AlgNumSubSystem} from "../../sketcher/constr/AlgNumSystem"; +import {AlgNumSubSystem, SolveStatus} from "../../sketcher/constr/AlgNumSystem"; import Vector from "math/vector"; import CSys from "math/csys"; -import {AssemblyCSysNode, AssemblyNode} from "./assembly"; +import {AssemblyNode} from "./assembly"; import {ISolveStage} from "../../sketcher/constr/solvableObject"; -import {MObject} from "../model/mobject"; import {MShell} from "../model/mshell"; -import {Constraints3D} from "./constraints3d"; +import {AssemblyCSysNode} from "./nodes/assemblyCSysNode"; -export function solveAssembly(constraints: AlgNumConstraint[]) { +export function solveAssembly(stages: AlgNumConstraint[][]): SolveStatus { + + // temporary solve everything in one stage + const constraints = [].concat(...stages); const objects = new Set(); - constraints.forEach(c => c.objects.forEach(o => objects.add(o))); const stage: ISolveStage = { @@ -33,11 +34,8 @@ export function solveAssembly(constraints: AlgNumConstraint[]) { o.reset(); }); - // const algNumConstraint = new AlgNumConstraint(Constraints3D.FaceParallel, objects); const system = new AlgNumSubSystem(() => 0.001, val => val, stage); - // __DEBUG__.AddNormal(face1.csys.origin, new Vector().set3(objects[0].normal.map(p => p.get()))) - // __DEBUG__.AddNormal(face2.csys.origin, new Vector().set3(objects[1].normal.map(p => p.get()))) system.startTransaction(); constraints.forEach(c => system.addConstraint(c)); @@ -60,17 +58,18 @@ export function solveAssembly(constraints: AlgNumConstraint[]) { } }); - system.prepare(); - system.solveFine(); system.finishTransaction(); + system.solveFine(); - // __DEBUG__.AddNormal(face1.csys.origin, new Vector().set3(objects[0].normal.map(p => p.get()))) - // __DEBUG__.AddNormal(face2.csys.origin, new Vector().set3(objects[1].normal.map(p => p.get()))) - - roots.forEach(root => { - applyResults(root, root.assemblyNodes.location); - }); + if (system.solveStatus.success) { + roots.forEach(root => { + applyResults(root, root.assemblyNodes.location); + }); + } else { + console.log("Assembly system haven't been solved, locations won't be updated"); + } + return system.solveStatus; } diff --git a/web/app/cad/assembly/constraints3d.ts b/web/app/cad/assembly/constraints3d.ts index 08ac3f94..f725f7b1 100644 --- a/web/app/cad/assembly/constraints3d.ts +++ b/web/app/cad/assembly/constraints3d.ts @@ -3,6 +3,11 @@ import {NoIcon} from "../../sketcher/icons/NoIcon"; import {AlgNumConstraint, ConstantsDefinitions, ConstraintSchema} from "../../sketcher/constr/ANConstraints"; import {MObject} from "../model/mobject"; import {SolvableObject} from "../../sketcher/constr/solvableObject"; +import {AssemblyNode} from "./assembly"; +import {EndPoint} from "../../sketcher/shapes/point"; +import {Circle} from "../../sketcher/shapes/circle"; +import {Arc} from "../../sketcher/shapes/arc"; + export const Constraints3D = { @@ -47,13 +52,70 @@ export const Constraints3D = { }, + FaceToFace: { + id: 'FaceToFace', + name: 'Face To Face', + icon: NoIcon, + + selectionMatcher: { + selector: 'matchAll', + types: ['face'], + minQuantity: 2 + }, + + defineAssemblyScope: ([face1, face2]) => { + return [ + face1.assemblyNodes.plane, + face2.assemblyNodes.plane, + ]; + }, + + defineParamsScope: ([plane1, plane2], cb) => { + plane1.visitParams(cb); + plane2.visitParams(cb); + }, + + collectPolynomials: (polynomials, params) => { + + const [ + nx1, ny1, nz1, w1, nx2, ny2, nz2, w2 + ] = params; + + polynomials.push( + new Polynomial(1) + .monomial() + .term(nx1, POW_1_FN) + .term(nx2, POW_1_FN) + .monomial() + .term(ny1, POW_1_FN) + .term(ny2, POW_1_FN) + .monomial() + .term(nz1, POW_1_FN) + .term(nz2, POW_1_FN) + + ); + polynomials.push( + new Polynomial() + .monomial() + .term(w1, POW_1_FN) + .monomial() + .term(w2, POW_1_FN) + ); + + } + + }, + UnitVectorConsistency: { id: 'UnitVectorConsistency', name: 'UnitVectorConsistency', icon: NoIcon, defineParamsScope: ([vec], cb) => { - vec.visitParams(cb); + //don't change to generic way it can a plane + cb(vec.x); + cb(vec.y); + cb(vec.z); }, collectPolynomials: (polynomials, params) => { @@ -162,6 +224,80 @@ export const Constraints3D = { }, }, + RigidBodyPlaneLink: { + id: 'RigidBodyPlaneLink', + name: 'RigidBodyPlaneLink', + icon: NoIcon, + + defineParamsScope: ([csys, plane], cb) => { + csys.visitParams(cb); + plane.visitParams(cb); + }, + + collectPolynomials: (polynomials, params, _, objects) => { + const [csys, plane] = objects; + + const n = plane.getNormal(); + const wStar = plane.getDepth(); + + const {x: xStar, y: yStar, z: zStar} = n.multiply(wStar); + + const [ox, oy, oz, ix, iy, iz, jx, jy, jz, kx, ky, kz, x, y, z, w] = params; + + // out.x = this.mxx * x + this.mxy * y + this.mxz * z + this.tx; + // out.y = this.myx * x + this.myy * y + this.myz * z + this.ty; + // out.z = this.mzx * x + this.mzy * y + this.mzz * z + this.tz; + + polynomials.push( + new Polynomial(0) + .monomial(-1) + .term(x, POW_1_FN) + .term(w, POW_1_FN) + .monomial(xStar) + .term(ix, POW_1_FN) + .monomial(yStar) + .term(jx, POW_1_FN) + .monomial(zStar) + .term(kx, POW_1_FN) + .monomial() + .term(ox, POW_1_FN) + ); + + polynomials.push( + new Polynomial(0) + .monomial(-1) + .term(y, POW_1_FN) + .term(w, POW_1_FN) + .monomial(xStar) + .term(iy, POW_1_FN) + .monomial(yStar) + .term(jy, POW_1_FN) + .monomial(zStar) + .term(ky, POW_1_FN) + .monomial() + .term(oy, POW_1_FN) + + ); + + polynomials.push( + new Polynomial(0) + .monomial(-1) + .term(z, POW_1_FN) + .term(w, POW_1_FN) + .monomial(xStar) + .term(iz, POW_1_FN) + .monomial(yStar) + .term(jz, POW_1_FN) + .monomial(zStar) + .term(kz, POW_1_FN) + .monomial() + .term(oz, POW_1_FN) + + ); + + } + }, + RigidBodyLink3x3: { id: 'RigidBodyLink3x3', name: 'RigidBodyLink3x3', @@ -231,18 +367,103 @@ export const Constraints3D = { } }, + RigidBodyLink4x4: { + id: 'RigidBodyLink4x4', + name: 'RigidBodyLink4x4', + icon: NoIcon, + + defineParamsScope: ([csys, vec], cb) => { + cb(csys.ox); + cb(csys.oy); + cb(csys.oz); + cb(csys.ix); + cb(csys.iy); + cb(csys.iz); + cb(csys.jx); + cb(csys.jy); + cb(csys.jz); + cb(csys.kx); + cb(csys.ky); + cb(csys.kz); + vec.visitParams(cb); + }, + + collectPolynomials: (polynomials, params, _, objects) => { + const [csys, vec] = objects; + + const {x: xStar, y: yStar, z: zStar} = vec.getVector(); + + const [ox, oy, oz, ix, iy, iz, jx, jy, jz, kx, ky, kz, x, y, z] = params; + + // out.x = this.mxx * x + this.mxy * y + this.mxz * z + this.tx; + // out.y = this.myx * x + this.myy * y + this.myz * z + this.ty; + // out.z = this.mzx * x + this.mzy * y + this.mzz * z + this.tz; + + polynomials.push( + new Polynomial(0) + .monomial(-1) + .term(x, POW_1_FN) + .monomial(xStar) + .term(ix, POW_1_FN) + .monomial(yStar) + .term(jx, POW_1_FN) + .monomial(zStar) + .term(kx, POW_1_FN) + .monomial() + .term(ox, POW_1_FN) + + ); + + polynomials.push( + new Polynomial(0) + .monomial(-1) + .term(y, POW_1_FN) + .monomial(xStar) + .term(iy, POW_1_FN) + .monomial(yStar) + .term(jy, POW_1_FN) + .monomial(zStar) + .term(ky, POW_1_FN) + .monomial() + .term(oy, POW_1_FN) + + + ); + + polynomials.push( + new Polynomial(0) + .monomial(-1) + .term(z, POW_1_FN) + .monomial(xStar) + .term(iz, POW_1_FN) + .monomial(yStar) + .term(jz, POW_1_FN) + .monomial(zStar) + .term(kz, POW_1_FN) + .monomial() + .term(oz, POW_1_FN) + + ); + + } + }, }; export interface AssemblyConstraintSchema extends ConstraintSchema { - defineAssemblyScope: (objects: MObject[]) => SolvableObject[], + selectionMatcher?: { + selector: string, + types: any[], + minQuantity: number + }; + defineAssemblyScope: (objects: MObject[]) => AssemblyNode[], } export function createAssemblyConstraint(schema: AssemblyConstraintSchema, objects: MObject[], constants?: ConstantsDefinitions, - internal?: boolean = false) { + internal: boolean = false) { return new AlgNumConstraint(schema, schema.defineAssemblyScope(objects), constants, internal); } \ No newline at end of file diff --git a/web/app/cad/assembly/nodes/assemblyCSysNode.ts b/web/app/cad/assembly/nodes/assemblyCSysNode.ts new file mode 100644 index 00000000..f1f98045 --- /dev/null +++ b/web/app/cad/assembly/nodes/assemblyCSysNode.ts @@ -0,0 +1,70 @@ +import {Param} from "../../../sketcher/shapes/param"; +import {Matrix3} from "math/l3space"; +import {MObject} from "../../model/mobject"; +import {AlgNumConstraint} from "../../../sketcher/constr/ANConstraints"; +import {Constraints3D} from "../constraints3d"; +import {AssemblyNode} from "../assembly"; + +export class AssemblyCSysNode extends AssemblyNode { + + ox = new Param(0, 'X'); + oy = new Param(0, 'Y'); + oz = new Param(0, 'Z'); + ix = new Param(1, 'X'); + iy = new Param(0, 'Y'); + iz = new Param(0, 'Z'); + jx = new Param(0, 'X'); + jy = new Param(1, 'Y'); + jz = new Param(0, 'Z'); + kx = new Param(0, 'X'); + ky = new Param(0, 'Y'); + kz = new Param(1, 'Z'); + getTransformation: () => Matrix3; + + constructor(model: MObject, getTransformation: () => Matrix3) { + super(model); + this.getTransformation = getTransformation; + } + + visitParams(cb) { + cb(this.ox); + cb(this.oy); + cb(this.oz); + cb(this.ix); + cb(this.iy); + cb(this.iz); + cb(this.jx); + cb(this.jy); + cb(this.jz); + cb(this.kx); + cb(this.ky); + cb(this.kz); + } + + reset() { + const mx = this.getTransformation(); + this.ox.set(mx.tx); + this.oy.set(mx.ty); + this.oz.set(mx.tz); + + this.ix.set(mx.mxx); + this.iy.set(mx.myx); + this.iz.set(mx.mzx); + + this.jx.set(mx.mxy); + this.jy.set(mx.myy); + this.jz.set(mx.mzy); + + this.kx.set(mx.mxz); + this.ky.set(mx.myz); + this.kz.set(mx.mzz); + + } + + createConsistencyConstraints() { + return [ + new AlgNumConstraint(Constraints3D.CSysConsistency, [this]) + ]; + } + +} \ No newline at end of file diff --git a/web/app/cad/assembly/nodes/assemblyPlaneNode.ts b/web/app/cad/assembly/nodes/assemblyPlaneNode.ts new file mode 100644 index 00000000..4022507d --- /dev/null +++ b/web/app/cad/assembly/nodes/assemblyPlaneNode.ts @@ -0,0 +1,53 @@ +import {Param} from "../../../sketcher/shapes/param"; +import Vector from "math/vector"; +import {MObject} from "../../model/mobject"; +import {AlgNumConstraint} from "../../../sketcher/constr/ANConstraints"; +import {Constraints3D} from "../constraints3d"; +import {AssemblyNode} from "../assembly"; +import {AssemblyCSysNode} from "./assemblyCSysNode"; + +export class AssemblyPlaneNode extends AssemblyNode { + + x = new Param(0, 'X'); + y = new Param(0, 'Y'); + z = new Param(0, 'Z'); + w = new Param(0, 'W'); + getNormal: () => Vector; + getDepth: () => number; + + constructor(model: MObject, getNormal: () => Vector, getDepth: () => number) { + super(model); + this.getNormal = getNormal; + this.getDepth = getDepth; + } + + visitParams(cb) { + cb(this.x); + cb(this.y); + cb(this.z); + cb(this.w); + } + + reset() { + const {x, y, z} = this.getNormal(); + const w = this.getDepth(); + this.x.set(x); + this.y.set(y); + this.z.set(z); + this.w.set(w); + } + + createConsistencyConstraints() { + return [ + new AlgNumConstraint(Constraints3D.UnitVectorConsistency, [this]) + ]; + } + + + createRigidBodyLink(body: AssemblyCSysNode) { + return [ + new AlgNumConstraint(Constraints3D.RigidBodyPlaneLink, [body, this]) + ]; + } + +} \ No newline at end of file diff --git a/web/app/cad/assembly/nodes/assemblyScalarNode.ts b/web/app/cad/assembly/nodes/assemblyScalarNode.ts new file mode 100644 index 00000000..20c60173 --- /dev/null +++ b/web/app/cad/assembly/nodes/assemblyScalarNode.ts @@ -0,0 +1,33 @@ +import {AssemblyNode} from "../assembly"; +import {Param} from "../../../sketcher/shapes/param"; +import {MObject} from "../../model/mobject"; +import {AlgNumConstraint} from "../../../sketcher/constr/ANConstraints"; +import {Constraints3D} from "../constraints3d"; +import {AssemblyCSysNode} from "./assemblyCSysNode"; + +export class AssemblyScalarNode extends AssemblyNode { + + param: Param; + getValue: () => number; + + constructor(model: MObject, debugSymbol: string, getValue: () => number) { + super(model); + this.param = new Param(0, debugSymbol); + this.getValue = getValue; + } + + reset() { + this.param.set(this.getValue()); + } + + visitParams(cb) { + cb(this.param); + } + + createRigidBodyLink(body: AssemblyCSysNode) { + return [ + // new AlgNumConstraint(Constraints3D.RigidTest, [body, this.model.assemblyNodes.normal, this]) + ]; + } + +} \ No newline at end of file diff --git a/web/app/cad/assembly/nodes/assemblyUnitVectorNode.ts b/web/app/cad/assembly/nodes/assemblyUnitVectorNode.ts new file mode 100644 index 00000000..c4370daa --- /dev/null +++ b/web/app/cad/assembly/nodes/assemblyUnitVectorNode.ts @@ -0,0 +1,46 @@ +import {Param} from "../../../sketcher/shapes/param"; +import Vector from "math/vector"; +import {MObject} from "../../model/mobject"; +import {AlgNumConstraint} from "../../../sketcher/constr/ANConstraints"; +import {Constraints3D} from "../constraints3d"; +import {AssemblyNode} from "../assembly"; +import {AssemblyCSysNode} from "./assemblyCSysNode"; + +export class AssemblyUnitVectorNode extends AssemblyNode { + + x = new Param(0, 'X'); + y = new Param(0, 'Y'); + z = new Param(0, 'Z'); + getVector: () => Vector; + + constructor(model: MObject, getVector: () => Vector) { + super(model); + this.getVector = getVector; + } + + visitParams(cb) { + cb(this.x); + cb(this.y); + cb(this.z); + } + + reset() { + const {x, y, z} = this.getVector(); + this.x.set(x); + this.y.set(y); + this.z.set(z); + } + + createConsistencyConstraints() { + return [ + new AlgNumConstraint(Constraints3D.UnitVectorConsistency, [this]) + ]; + } + + createRigidBodyLink(body: AssemblyCSysNode) { + return [ + // new AlgNumConstraint(Constraints3D.RigidBodyLink3x3, [body, this]) + ]; + } + +} \ No newline at end of file diff --git a/web/app/cad/assembly/nodes/assemblyVectorNode.ts b/web/app/cad/assembly/nodes/assemblyVectorNode.ts new file mode 100644 index 00000000..ab5bbe01 --- /dev/null +++ b/web/app/cad/assembly/nodes/assemblyVectorNode.ts @@ -0,0 +1,40 @@ +import {Param} from "../../../sketcher/shapes/param"; +import Vector from "math/vector"; +import {MObject} from "../../model/mobject"; +import {AlgNumConstraint} from "../../../sketcher/constr/ANConstraints"; +import {Constraints3D} from "../constraints3d"; +import {AssemblyNode} from "../assembly"; +import {AssemblyCSysNode} from "./assemblyCSysNode"; + +export class AssemblyVectorNode extends AssemblyNode { + + x = new Param(0, 'X'); + y = new Param(0, 'Y'); + z = new Param(0, 'Z'); + getVector: () => Vector; + + constructor(model: MObject, getVector: () => Vector) { + super(model); + this.getVector = getVector; + } + + visitParams(cb) { + cb(this.x); + cb(this.y); + cb(this.z); + } + + reset() { + const {x, y, z} = this.getVector(); + this.x.set(x); + this.y.set(y); + this.z.set(z); + } + + createRigidBodyLink(body: AssemblyCSysNode) { + return [ + new AlgNumConstraint(Constraints3D.RigidBodyLink4x4, [body, this]) + ]; + } + +} \ No newline at end of file diff --git a/web/app/cad/assembly/ui/AssemblyView.tsx b/web/app/cad/assembly/ui/AssemblyView.tsx new file mode 100644 index 00000000..8b1ca1cc --- /dev/null +++ b/web/app/cad/assembly/ui/AssemblyView.tsx @@ -0,0 +1,91 @@ +import React, {useContext, useEffect} from 'react'; +import {useStream} from "ui/effects"; +import {Status} from "ui/components/Status"; +import Folder from "ui/components/Folder"; +import {Constraints3D} from "../constraints3d"; +import {AppContext} from "../../dom/components/AppContext"; +import cx from 'classnames'; +import {NoIcon} from "../../../sketcher/icons/NoIcon"; +import ls from "../../../sketcher/components/ConstraintExplorer.less"; +import Fa from "ui/components/Fa"; +import {AssemblyConstraintDefinition} from "../assemblyConstraintDefinition"; +import {ApplicationContext} from "context"; + + +export function AssemblyView() { + + const ctx = useContext(AppContext); + const constraints = useStream(ctx => ctx.assemblyService.constraints$); + const status = useStream(ctx => ctx.assemblyService.status$); + + + return
+
+ Status: +
+ {constraints.map((stage, i) => + {stage.map((constr, j) => ) } + )} +
+ +} + +export function AssemblyConstraintButton({prefix='', constraint: c, ...props}: { + prefix: string, + constraint: AssemblyConstraintDefinition, + props?: React.HTMLAttributes +}) { + + const ctx: ApplicationContext = useContext(AppContext); + + const edit = (constraint) => { + if (constraint.editable) { + //... + } + }; + + const remove = constr => { + ctx.assemblyService.removeConstraint(constr); + }; + + const highlight = constr => { + ctx.services.marker.clear(); + constr.objects.forEach(id => { + const entity = ctx.cadRegistry.find(id); + if (entity) { + ctx.services.marker.markAdding(entity.TYPE, id); + } + }); + }; + + const withdraw = () => { + ctx.services.marker.clear(); + }; + + useEffect(() => withdraw, [c]); + + const schema = Constraints3D[c.typeId]; + if (schema === null) { + return
Invalid Constraint {c.typeId}
+ } + + const entities = c.objects.map(ctx.cadRegistry.find); + + const invalid = !!entities.find(x => !x); + + const Icon = schema.icon || NoIcon; + + return
schema.constants && edit(c)} + onMouseEnter={() => highlight(c)} + onMouseLeave={() => withdraw()} + {...props}> + + + {prefix} {schema.name} + + remove(c)}> + +
+ +} diff --git a/web/app/cad/assembly/ui/ModellerContextualActions.tsx b/web/app/cad/assembly/ui/ModellerContextualActions.tsx new file mode 100644 index 00000000..fb6c8a3f --- /dev/null +++ b/web/app/cad/assembly/ui/ModellerContextualActions.tsx @@ -0,0 +1,36 @@ +import React, {useContext} from 'react'; +import {AppContext} from "../../dom/components/AppContext"; +import {useStream} from "ui/effects"; +import {Dialog} from "ui/components/Dialog"; +import {AssemblyConstraintSchema, Constraints3D} from "../constraints3d"; +import {matchAvailableSubjects, MatchIndex, matchSelection} from "../../../sketcher/selectionMatcher"; + +export function ModellerContextualActions() { + + const ctx = useContext(AppContext); + + const selection: string[] = useStream(ctx => ctx.streams.selection.all); + + if (!selection || selection.length === 0) { + return null; + } + + const entities = selection.map(ctx.cadRegistry.find); + + const allConstraints = Object.values(Constraints3D) as AssemblyConstraintSchema[]; + const availableConstraints = matchAvailableSubjects(entities, allConstraints) as AssemblyConstraintSchema[]; + + if (availableConstraints.length === 0) { + return null; + } + + return {}}> + {availableConstraints.map( schema => ) } + ; +} diff --git a/web/app/cad/craft/craftPlugin.ts b/web/app/cad/craft/craftPlugin.ts index 906ff1e1..b84a174d 100644 --- a/web/app/cad/craft/craftPlugin.ts +++ b/web/app/cad/craft/craftPlugin.ts @@ -99,11 +99,16 @@ export function activate(ctx: CoreContext) { return runRequest(request); } } - + + function isEditingHistory() { + const mods = this.modifications$.value; + return mods && mods.pointer !== mods.history.length - 1; + } + ctx.craftService = { modify, modifyInHistoryAndStep, reset, rebuild, runRequest, runPipeline, historyTravel: historyTravel(modifications$), - modifications$, models$, update$ + modifications$, models$, update$, isEditingHistory }; // @ts-ignore @@ -256,7 +261,9 @@ interface CraftService { runRequest(request: OperationRequest): Promise; - runPipeline(history: OperationRequest[], beginIndex: number, endIndex: number): Promise + runPipeline(history: OperationRequest[], beginIndex: number, endIndex: number): Promise; + + isEditingHistory(): boolean; } interface HistoryTravel { diff --git a/web/app/cad/dom/components/ModellerContextualActions.tsx b/web/app/cad/dom/components/ModellerContextualActions.tsx deleted file mode 100644 index f1a66172..00000000 --- a/web/app/cad/dom/components/ModellerContextualActions.tsx +++ /dev/null @@ -1,377 +0,0 @@ -import React, {useContext} from 'react'; -import {AppContext} from "./AppContext"; -import {useStream} from "ui/effects"; -import {ApplicationContext} from "context"; -import {AlgNumSubSystem} from "../../../sketcher/constr/AlgNumSystem"; -import {ParallelConstraintIcon} from "../../../sketcher/icons/constraints/ConstraintIcons"; -import {DEG_RAD, makeAngle0_360} from "../../../math/math"; -import {AlgNumConstraint} from "../../../sketcher/constr/ANConstraints"; -import {Param} from "../../../sketcher/shapes/param"; -import {COS_FN, Polynomial, POW_1_FN, POW_2_FN, SIN_FN} from "../../../sketcher/constr/polynomial"; -import {Matrix3} from "math/l3space"; -import CSys from "math/csys"; -import Vector from "math/vector"; -import {Dialog} from "ui/components/Dialog"; -import {MObject} from "../../model/mobject"; -import {MBrepFace} from "../../model/mface"; -import {solveAssembly} from "../../assembly/assemblySolver"; -import {Constraints3D, createAssemblyConstraint} from "../../assembly/constraints3d"; - -export function ModellerContextualActions({}) { - - const ctx = useContext(AppContext); - - const faceSelection: string[] = useStream(ctx => ctx.streams.selection.face); - - if (faceSelection.length === 0) { - return null; - } - - const actions = []; - - if (faceSelection.length === 2) { - - actions.push(); - - } - - return {}}> - {actions} - ; -} - -const XConstraints3D = { - FaceParallel: { - id: 'FaceParallel', - name: 'FaceParallel', - icon: ParallelConstraintIcon, - - defineAssemblyScope: ([face1, face2], cb) => { - cb(face1.assemblyNodes.normal); - cb(face2.assemblyNodes.normal); - }, - - defineParamsScope: (objects, cb) => { - - const [face1W, face2W, csys1W, csys2W] = objects; - - const n1 = face1W.normal; - const n2 = face2W.normal; - - - const [nx1, ny1, nz1] = n1; - const [nx2, ny2, nz2] = n2; - - const csysParams1 = csys1W.params; - const [ - ox1, oy1, oz1, ix1, iy1, iz1, jx1, jy1, jz1, kx1, ky1, kz1 - ] = csysParams1; - - const csysParams2 = csys2W.params; - const [ - ox2, oy2, oz2, ix2, iy2, iz2, jx2, jy2, jz2, kx2, ky2, kz2 - ] = csysParams2; - - [ - nx1, ny1, nz1, nx2, ny2, nz2, - ox1, oy1, oz1, ix1, iy1, iz1, jx1, jy1, jz1, kx1, ky1, kz1, - ox2, oy2, oz2, ix2, iy2, iz2, jx2, jy2, jz2, kx2, ky2, kz2 - ].forEach(cb); - - }, - - collectPolynomials: (polynomials, params, constants, [face1, face2, csys1, csys2]) => { - - const [ - nx1, ny1, nz1, nx2, ny2, nz2, - ox1, oy1, oz1, ix1, iy1, iz1, jx1, jy1, jz1, kx1, ky1, kz1, - ox2, oy2, oz2, ix2, iy2, iz2, jx2, jy2, jz2, kx2, ky2, kz2 - ] = params; - - polynomials.push( - new Polynomial(1) - .monomial() - .term(nx1, POW_1_FN) - .term(nx2, POW_1_FN) - .monomial() - .term(ny1, POW_1_FN) - .term(ny2, POW_1_FN) - .monomial() - .term(nz1, POW_1_FN) - .term(nz2, POW_1_FN) - ); - - rigidBodyLink3x3( - [ix1, iy1, iz1, jx1, jy1, jz1, kx1, ky1, kz1], - csys1.csys, - face1.normal - ).forEach(p => polynomials.push(p)); - - rigidBodyLink3x3( - [ix2, iy2, iz2, jx2, jy2, jz2, kx2, ky2, kz2], - csys2.csys, - face2.normal - ).forEach(p => polynomials.push(p)); - - polynomials.push( - new Polynomial(-1) - .monomial() - .term(nx1, POW_2_FN) - .monomial() - .term(ny1, POW_2_FN) - .monomial() - .term(nz1, POW_2_FN) - ); - - polynomials.push( - new Polynomial(-1) - .monomial() - .term(nx2, POW_2_FN) - .monomial() - .term(ny2, POW_2_FN) - .monomial() - .term(nz2, POW_2_FN) - ); - - } - - }, - -}; - -function vectorParams(vec) { - const {x, y, z} = vec; - return [new Param(x, 'X'), new Param(y, 'Y'), new Param(z, 'Z')]; -} - -function csysParams(csys) { - const {x, y, z} = csys.origin; - return [ - new Param(x, 'X'), - new Param(y, 'Y'), - new Param(z, 'Z'), - new Param(csys.x.x, 'X'), - new Param(csys.x.y, 'Y'), - new Param(csys.x.z, 'Z'), - new Param(csys.y.x, 'X'), - new Param(csys.y.y, 'Y'), - new Param(csys.y.z, 'Z'), - new Param(csys.z.x, 'X'), - new Param(csys.z.y, 'Y'), - new Param(csys.z.z, 'Z') - ]; -} - -function faceWrapper(face: MBrepFace) { - - return { - constraints: new Set(), - normal: vectorParams(face.normal()), - face, - visitParams(cb) { - this.normal.forEach(cb); - } - - } - -} - - -function csysWrapper(csys: CSys) { - - return { - constraints: new Set(), - params: csysParams(csys), - csys, - visitParams(cb) { - this.params.forEach(cb); - } - } - -} -function faceParallel(ctx: ApplicationContext, faceSelection: string[]) { - - const [face1, face2] = faceSelection.map(id => ctx.cadRegistry.find(id)); - - const constraints = [ - createAssemblyConstraint(Constraints3D.FaceParallel, [face1, face2]) - ]; - - solveAssembly(constraints); -} - -function faceParallelLegacy(ctx: ApplicationContext, faceSelection: string[]) { - - const [face1, face2] = faceSelection.map(id => ctx.cadRegistry.find(id)); - - const stage = {}; - const objects = [ - faceWrapper(face1), - faceWrapper(face2), - csysWrapper(face1.shell.csys), - csysWrapper(face2.shell.csys), - ]; - objects.forEach(o => o.stage = stage); - stage.objects = objects; - const algNumConstraint = new AlgNumConstraint(XConstraints3D.FaceParallel, objects); - - const system = new AlgNumSubSystem(() => 0.001, val => val, stage); - // __DEBUG__.AddNormal(face1.csys.origin, new Vector().set3(objects[0].normal.map(p => p.get()))) - // __DEBUG__.AddNormal(face2.csys.origin, new Vector().set3(objects[1].normal.map(p => p.get()))) - - system.startTransaction(); - system.addConstraint(algNumConstraint); - system.prepare(); - system.solveFine(); - system.finishTransaction(); - - __DEBUG__.AddNormal(face1.csys.origin, new Vector().set3(objects[0].normal.map(p => p.get()))) - __DEBUG__.AddNormal(face2.csys.origin, new Vector().set3(objects[1].normal.map(p => p.get()))) - - - - function applyResults(shell, targetCsysParams, normal) { - const [ - ox, oy, oz, ix, iy, iz, jx, jy, jz, kx, ky, kz - ] = targetCsysParams.map(p => p.get()); - - const targetCsys = new CSys( - new Vector(ox, oy, oz), - new Vector(ix, iy, iz), - new Vector(jx, jy, jz), - new Vector(kx, ky, kz), - ); - - const basis = [ - new Vector(ix, iy, iz), - new Vector(jx, jy, jz), - new Vector(kx, ky, kz), - ]; - - // __DEBUG__.AddCSys(shell.csys); - __DEBUG__.AddCSys(targetCsys); - - const tr = shell.csys.inTransformation3x3; - basis.forEach(r => tr._apply(r)); - - shell.location$.update(csys => { - return targetCsys; - }); - // shell.location$.mutate(csys => { - // csys.x = basis[0]; - // csys.y = basis[1]; - // csys.z = basis[2]; - // csys.origin = new Vector(ox, oy, oz)._minus(shell.csys.origin); - // }); - - } - - applyResults(face1.shell, objects[2].params, new Vector().set3(objects[0].normal.map(p => p.get()))); - applyResults(face2.shell, objects[3].params, new Vector().set3(objects[1].normal.map(p => p.get()))); - - -} - - -function rigidBodyLink3x3(csysParams, csys, vector) { - const [ix, iy, iz, jx, jy, jz, kx, ky, kz] = csysParams; - const [x, y, z] = vector; - - // const [nStarX, nStarY, nStarZ] = csys.inTransformation3x3.apply3(vector.map(p => p.get())); - const [nStarX, nStarY, nStarZ] = vector.map(p => p.get()); - // out.x = this.mxx * x + this.mxy * y + this.mxz * z + this.tx; - // out.y = this.myx * x + this.myy * y + this.myz * z + this.ty; - // out.z = this.mzx * x + this.mzy * y + this.mzz * z + this.tz; - - return [ - new Polynomial(0) - .monomial(-1) - .term(x, POW_1_FN) - .monomial(nStarX) - .term(ix, POW_1_FN) - .monomial(nStarY) - .term(jx, POW_1_FN) - .monomial(nStarZ) - .term(kx, POW_1_FN), - - new Polynomial(0) - .monomial(-1) - .term(y, POW_1_FN) - .monomial(nStarX) - .term(iy, POW_1_FN) - .monomial(nStarY) - .term(jy, POW_1_FN) - .monomial(nStarZ) - .term(ky, POW_1_FN), - - new Polynomial(0) - .monomial(-1) - .term(z, POW_1_FN) - .monomial(nStarX) - .term(iz, POW_1_FN) - .monomial(nStarY) - .term(jz, POW_1_FN) - .monomial(nStarZ) - .term(kz, POW_1_FN), - - - new Polynomial(0) - .monomial() - .term(ix, POW_1_FN) - .term(jx, POW_1_FN) - .monomial() - .term(iy, POW_1_FN) - .term(jy, POW_1_FN) - .monomial() - .term(iz, POW_1_FN) - .term(jz, POW_1_FN), - - new Polynomial(0) - .monomial() - .term(ix, POW_1_FN) - .term(kx, POW_1_FN) - .monomial() - .term(iy, POW_1_FN) - .term(ky, POW_1_FN) - .monomial() - .term(iz, POW_1_FN) - .term(kz, POW_1_FN), - - new Polynomial(0) - .monomial() - .term(jx, POW_1_FN) - .term(kx, POW_1_FN) - .monomial() - .term(jy, POW_1_FN) - .term(ky, POW_1_FN) - .monomial() - .term(jz, POW_1_FN) - .term(kz, POW_1_FN), - - new Polynomial(-1) - .monomial() - .term(ix, POW_2_FN) - .monomial() - .term(iy, POW_2_FN) - .monomial() - .term(iz, POW_2_FN), - - new Polynomial(-1) - .monomial() - .term(jx, POW_2_FN) - .monomial() - .term(jy, POW_2_FN) - .monomial() - .term(jz, POW_2_FN), - - new Polynomial(-1) - .monomial() - .term(kx, POW_2_FN) - .monomial() - .term(ky, POW_2_FN) - .monomial() - .term(kz, POW_2_FN), - - - ] -} diff --git a/web/app/cad/dom/components/View3d.jsx b/web/app/cad/dom/components/View3d.jsx index d8e80c96..f44f2955 100644 --- a/web/app/cad/dom/components/View3d.jsx +++ b/web/app/cad/dom/components/View3d.jsx @@ -20,7 +20,6 @@ import SketcherOperationWizard from "../../../sketcher/components/SketcherOperat import {ToastContainer} from "react-toastify"; import 'react-toastify/dist/ReactToastify.css'; import {ContributedComponents} from "./ContributedComponents"; -import {ModellerContextualActions} from "./ModellerContextualActions"; export default class View3d extends React.Component { @@ -59,7 +58,6 @@ export default class View3d extends React.Component { -
diff --git a/web/app/cad/init/startApplication.js b/web/app/cad/init/startApplication.js index f8524154..9c73e50e 100644 --- a/web/app/cad/init/startApplication.js +++ b/web/app/cad/init/startApplication.js @@ -37,6 +37,7 @@ import * as DebugPlugin from "../debugPlugin"; import * as ExpressionsPlugin from "../expressions/expressionsPlugin"; import * as PartOperationsPlugin from "../part/partOperationsPlugin"; import * as LocationPlugin from "../location/locationPlugin"; +import * as AssemblyPlugin from "../assembly/assemblyPlugin"; export default function startApplication(callback) { @@ -76,6 +77,7 @@ export default function startApplication(callback) { DebugPlugin, PartOperationsPlugin, LocationPlugin, + AssemblyPlugin, RemotePartsPlugin, ViewSyncPlugin, WizardSelectionPlugin diff --git a/web/app/cad/model/mface.ts b/web/app/cad/model/mface.ts index fc9bb0c4..dbd4eb35 100644 --- a/web/app/cad/model/mface.ts +++ b/web/app/cad/model/mface.ts @@ -7,7 +7,10 @@ import CSys from 'math/csys'; import {MSketchLoop} from './mloop'; import {ProductionInfo} from './productionInfo'; import {MBrepShell, MShell} from "./mshell"; -import {AssemblyUnitVectorNode} from "../assembly/assembly"; +import {AssemblyUnitVectorNode} from "../assembly/nodes/assemblyUnitVectorNode"; +import {AssemblyScalarNode} from "../assembly/nodes/assemblyScalarNode"; +import {AssemblyVectorNode} from "../assembly/nodes/assemblyVectorNode"; +import {AssemblyPlaneNode} from "../assembly/nodes/assemblyPlaneNode"; export class MFace extends MObject { @@ -20,7 +23,9 @@ export class MFace extends MObject { brepFace: any; assemblyNodes: { - normal: AssemblyUnitVectorNode + // normal: AssemblyUnitVectorNode + plane: AssemblyPlaneNode, + // w: AssemblyScalarNode }; private _csys: any; @@ -38,15 +43,17 @@ export class MFace extends MObject { this.sketchLoops = []; this._csys = csys; this.assemblyNodes = { - normal: new AssemblyUnitVectorNode(this, () => this.normal()) + // normal: new AssemblyUnitVectorNode(this, () => this.normal()), + // w: new AssemblyScalarNode(this, 'W', () => this.depth()) + plane: new AssemblyPlaneNode(this, () => this.normal(), () => this.depth()) }; } - normal() { + normal(): Vector { return this.csys.z; } - depth() { + depth(): number { this.evalCSys(); return this.w; } diff --git a/web/app/cad/model/mshell.ts b/web/app/cad/model/mshell.ts index 76362c9c..7461e17f 100644 --- a/web/app/cad/model/mshell.ts +++ b/web/app/cad/model/mshell.ts @@ -5,7 +5,7 @@ import {MVertex} from './mvertex'; import CSys from 'math/csys'; import {Matrix3} from "math/l3space"; import {state, StateStream} from "lstream"; -import {AssemblyCSysNode} from "../assembly/assembly"; +import {AssemblyCSysNode} from "../assembly/nodes/assemblyCSysNode"; export class MShell extends MObject { diff --git a/web/app/cad/projectManager/projectManagerPlugin.ts b/web/app/cad/projectManager/projectManagerPlugin.ts index af900553..74ea6f53 100644 --- a/web/app/cad/projectManager/projectManagerPlugin.ts +++ b/web/app/cad/projectManager/projectManagerPlugin.ts @@ -4,6 +4,7 @@ import exportTextData from '../../../../modules/gems/exportTextData'; import {SketchFormat_V3} from "../../sketcher/io"; import {ApplicationContext} from "context"; import {OperationRequest} from "../craft/craftPlugin"; +import {AssemblyConstraintDefinition} from "../assembly/assemblyConstraintDefinition"; export function activate(ctx: ApplicationContext) { @@ -202,6 +203,8 @@ export interface ProjectModel { expressions: string + assembly?: AssemblyConstraintDefinition[][]; + } export interface ModelBundle { diff --git a/web/app/cad/projectPlugin.ts b/web/app/cad/projectPlugin.ts index 1eae6b7f..f7bc994a 100644 --- a/web/app/cad/projectPlugin.ts +++ b/web/app/cad/projectPlugin.ts @@ -40,7 +40,10 @@ export function initProjectService(ctx: CoreContext, id: string, hints: any) { function save() { let data = { history: ctx.craftService.modifications$.value.history, - expressions: ctx.expressionService.script$.value + expressions: ctx.expressionService.script$.value, + + // @ts-ignore we deliberately don't uplift the type to the ApplicationContext in order to be able to use ProjectService in the headless mode + assembly: ctx.assemblyService && ctx.assemblyService.getConstraints() }; ctx.storageService.set(projectStorageKey(), JSON.stringify(data)); } @@ -65,6 +68,13 @@ export function initProjectService(ctx: CoreContext, id: string, hints: any) { if (data.history) { ctx.craftService.reset(data.history); } + + // @ts-ignore we deliberately don't uplift the type to the ApplicationContext in order to be able to use ProjectService in the headless mode + if (data.assembly && ctx.assemblyService) { + // @ts-ignore + ctx.assemblyService.loadConstraints(data.assembly); + } + } function empty() { diff --git a/web/app/cad/scene/entityContextPlugin.js b/web/app/cad/scene/entityContextPlugin.js index 7e7b780d..ed0b47dc 100644 --- a/web/app/cad/scene/entityContextPlugin.js +++ b/web/app/cad/scene/entityContextPlugin.js @@ -3,6 +3,7 @@ import {state} from 'lstream'; import {addToListInMap} from 'gems/iterables'; import {EMPTY_ARRAY} from '../../../../modules/gems/iterables'; import {DATUM, FACE, SHELL, SKETCH_OBJECT, EDGE, LOOP} from './entites'; +import {combine} from "../../../../modules/lstream"; export const SELECTABLE_ENTITIES = [FACE, EDGE, SKETCH_OBJECT, DATUM, SHELL]; @@ -11,6 +12,7 @@ export function defineStreams(ctx) { SELECTABLE_ENTITIES.forEach(entity => { ctx.streams.selection[entity] = state([]); }); + ctx.streams.selection.all = combine(...Object.values(ctx.streams.selection)).map(selection => [].concat(...selection)).throttle(); } export function activate(ctx) { diff --git a/web/app/cad/sketch/sketcherPlugin.ts b/web/app/cad/sketch/sketcherPlugin.ts index a3df08a7..ae748e65 100644 --- a/web/app/cad/sketch/sketcherPlugin.ts +++ b/web/app/cad/sketch/sketcherPlugin.ts @@ -5,6 +5,7 @@ import sketcherUIContrib from './sketcherUIContrib'; import initReassignSketchMode from './reassignSketchMode'; import {Viewer} from "../../sketcher/viewer2d"; import {IO} from "../../sketcher/io"; +import {Generator} from "../../sketcher/id-generator"; export function defineStreams(ctx) { ctx.streams.sketcher = { @@ -67,6 +68,7 @@ export function activate(ctx) { services.storage.set(ctx.projectService.sketchStorageKey(sketchId), viewer.io.serializeSketch({ expressionsSignature: signature })); + Generator.resetIDGenerator(); } catch (e) { console.error(e); return null; diff --git a/web/app/sketcher/actions/index.js b/web/app/sketcher/actions/index.js index f03044d0..6be3a3a9 100644 --- a/web/app/sketcher/actions/index.js +++ b/web/app/sketcher/actions/index.js @@ -1,5 +1,5 @@ import constraintActions from "./constraintActions"; -import {getDescription, MatchIndex, matchSelection} from "../selectionMatcher"; +import {getDescription, matchAvailableSubjects, MatchIndex, matchSelection} from "../selectionMatcher"; import {toast} from "react-toastify"; import operationActions from "./operationActions"; import constraintGlobalActions from "./constraintGlobalActions"; @@ -35,19 +35,7 @@ ALL_ACTIONS.forEach(a => index[a.id] = a); Object.freeze(index); export function matchAvailableActions(selection) { - - let matched = []; - let matchIndex = new MatchIndex(selection); - - if (selection.length) { - for (let action of ALL_CONTEXTUAL_ACTIONS) { - if (action.selectionMatcher && matchSelection(action.selectionMatcher, matchIndex, true)) { - matched.push(action); - } - } - } - - return matched; + return matchAvailableSubjects(selection, ALL_CONTEXTUAL_ACTIONS); } export function getSketcherAction(actionId) { diff --git a/web/app/sketcher/components/SketchObjectExplorer.jsx b/web/app/sketcher/components/SketchObjectExplorer.jsx index 4c59deb0..82ecd088 100644 --- a/web/app/sketcher/components/SketchObjectExplorer.jsx +++ b/web/app/sketcher/components/SketchObjectExplorer.jsx @@ -10,7 +10,7 @@ export function SketchObjectExplorer() { const [modification, setModification] = useState(0); const objects = useStream(ctx => ctx.viewer.streams.objects); - const selection = useStream(ctx.viewer.streams.selection); + const selection = useStream(ctx => ctx.viewer.streams.selection); const ctx = useContext(SketcherAppContext); if (!objects || !selection) { diff --git a/web/app/sketcher/constr/ANConstraints.ts b/web/app/sketcher/constr/ANConstraints.ts index 25325956..fa61cf6e 100644 --- a/web/app/sketcher/constr/ANConstraints.ts +++ b/web/app/sketcher/constr/ANConstraints.ts @@ -36,6 +36,7 @@ import { import {ISolveStage, SolvableObject} from "./solvableObject"; import {SketchObject} from "../shapes/sketch-object"; import {IconType} from "react-icons"; +import {ConstraintAnnotation} from "./constraintAnnotation"; export const ConstraintDefinitions // : { @@ -966,7 +967,7 @@ export interface ConstraintSchema { } }; - createAnnotations?: (objects: SolvableObject[], constraintInstance: AlgNumConstraint) => SketchObject[]; + createAnnotations?: (objects: SolvableObject[], constraintInstance: AlgNumConstraint) => ConstraintAnnotation[]; defineParamsScope: (object: SolvableObject[], cb: (param: Param) => void) => void; @@ -989,9 +990,9 @@ export class AlgNumConstraint { schema: ConstraintSchema; params: Param[]; stage: ISolveStage; - private annotations: SketchObject[]; + annotations: ConstraintAnnotation[]; - constructor(schema: ConstraintSchema, objects: SolvableObject[], constants?: ConstantsDefinitions, internal?: boolean = false) { + constructor(schema: ConstraintSchema, objects: SolvableObject[], constants?: ConstantsDefinitions, internal: boolean = false) { this.id = schema.id + ':' + (AlgNumConstraint.Counter ++); // only for debug purposes - not persisted this.objects = objects; this.constants = constants; @@ -1038,7 +1039,7 @@ export class AlgNumConstraint { } } - write() { + write(): ConstraintSerialization { return { typeId: this.schema.id, objects: this.objects.map(o => o.id), @@ -1048,7 +1049,7 @@ export class AlgNumConstraint { } } - static read({typeId, objects, constants, annotations}, index) { + static read({typeId, objects, constants, annotations}: ConstraintSerialization, index: {[key: string]: SolvableObject}) { const schema = ConstraintDefinitions[typeId]; if (!schema) { throw "constraint schema " + typeId + " doesn't exist"; @@ -1106,3 +1107,12 @@ export class AlgNumConstraint { this.constants[key] = value + ''; // only string are allowed here } } + + +export interface ConstraintSerialization { + typeId: string; + objects: string[]; + constants: ConstantsDefinitions; + stage: number; + annotations?: any +} \ No newline at end of file diff --git a/web/app/sketcher/constr/AlgNumSystem.ts b/web/app/sketcher/constr/AlgNumSystem.ts index bb31e3a6..4e8478c7 100644 --- a/web/app/sketcher/constr/AlgNumSystem.ts +++ b/web/app/sketcher/constr/AlgNumSystem.ts @@ -626,7 +626,7 @@ class PolynomialResidual { } -interface SolveStatus { +export interface SolveStatus { success: boolean; - error: number + error: number; } \ No newline at end of file diff --git a/web/app/sketcher/constr/constraintAnnotation.ts b/web/app/sketcher/constr/constraintAnnotation.ts new file mode 100644 index 00000000..1a89311c --- /dev/null +++ b/web/app/sketcher/constr/constraintAnnotation.ts @@ -0,0 +1,6 @@ +export interface ConstraintAnnotation { + + save(): T; + + load(data: T); +} \ No newline at end of file diff --git a/web/app/sketcher/selectionMatcher.js b/web/app/sketcher/selectionMatcher.js index f26ea139..bd9f84d1 100644 --- a/web/app/sketcher/selectionMatcher.js +++ b/web/app/sketcher/selectionMatcher.js @@ -1,3 +1,20 @@ + +export function matchAvailableSubjects(selection, subjects) { + + let matched = []; + let matchIndex = new MatchIndex(selection); + + if (selection.length) { + for (let action of subjects) { + if (action.selectionMatcher && matchSelection(action.selectionMatcher, matchIndex, true)) { + matched.push(action); + } + } + } + + return matched; +} + export function matchSelection(definition, matchIndex, fast) { const selection = matchIndex.selection; if (definition.selector === 'function') { @@ -12,7 +29,7 @@ export function matchSelection(definition, matchIndex, fast) { let hit = false; for (let constructor of types) { - if (constructor.prototype._class === obj._class) { + if (typeToString(constructor) === obj.TYPE) { hit = true; break; } @@ -70,7 +87,7 @@ export function getDescription(definition) { } function stringifyTypes(types, minQuantity) { - return types.map(t => t.prototype.TYPE + (minQuantity > 1 ? 's' : '')).join(' or '); + return types.map(t => typeToString(t) + (minQuantity > 1 ? 's' : '')).join(' or '); } export class MatchIndex { @@ -82,13 +99,13 @@ export class MatchIndex { constructor(selection) { this.selection = selection; selection.forEach(obj => { - let info = this.typeMap.get(obj._class); + let info = this.typeMap.get(obj.TYPE); if (!info) { info = { hits: 0, objects: [] }; - this.typeMap.set(obj._class, info); + this.typeMap.set(obj.TYPE, info); } info.objects.push(obj); }) @@ -102,7 +119,7 @@ export class MatchIndex { mark(types, quantity) { for (let type of types) { - const info = this.typeMap.get(type.prototype._class); + const info = this.typeMap.get(typeToString(type)); if (!info) { continue; } @@ -123,4 +140,12 @@ export class MatchIndex { return this.selection.length === this.overallHits; } +} + +function typeToString(type) { + if (typeof type === 'string') { + return type; + } else { + return type.prototype.TYPE; + } } \ No newline at end of file diff --git a/web/app/sketcher/shapes/annotations/angleAnnotation.js b/web/app/sketcher/shapes/annotations/angleAnnotation.ts similarity index 81% rename from web/app/sketcher/shapes/annotations/angleAnnotation.js rename to web/app/sketcher/shapes/annotations/angleAnnotation.ts index 33c34f8f..bf215e8c 100644 --- a/web/app/sketcher/shapes/annotations/angleAnnotation.js +++ b/web/app/sketcher/shapes/annotations/angleAnnotation.ts @@ -1,7 +1,11 @@ import {AngleBetweenDimension, DiameterDimension, LinearDimension} from "../dim"; import {Styles} from "../../styles"; +import {ConstraintAnnotation} from "../../constr/constraintAnnotation"; +import {AlgNumConstraint} from "../../constr/ANConstraints"; -export class AngleBetweenAnnotation extends AngleBetweenDimension { +export class AngleBetweenAnnotation extends AngleBetweenDimension implements ConstraintAnnotation<{offset: number}> { + + constraint: AlgNumConstraint; constructor(a, b, constraint) { super(a, b); @@ -27,7 +31,9 @@ AngleBetweenAnnotation.prototype.TYPE = 'AngleBetweenAnnotation'; AngleBetweenAnnotation.prototype._class = 'TCAD.TWO.AngleBetweenAnnotation'; -export class AngleAbsoluteAnnotation extends AngleBetweenDimension { +export class AngleAbsoluteAnnotation extends AngleBetweenDimension implements ConstraintAnnotation<{offset: number}> { + + constraint: AlgNumConstraint; constructor(segment, constraint) { super({ @@ -90,7 +96,9 @@ export class AngleAbsoluteAnnotation extends AngleBetweenDimension { AngleAbsoluteAnnotation.prototype._class = 'AngleAbsoluteAnnotation'; -export class LengthAnnotation extends LinearDimension { +export class LengthAnnotation extends LinearDimension implements ConstraintAnnotation<{offset: number}> { + + constraint: AlgNumConstraint; constructor(segment, constraint) { super(segment.a, segment.b); @@ -116,7 +124,9 @@ LengthAnnotation.prototype.TYPE = 'LengthAnnotation'; LengthAnnotation.prototype._class = 'TCAD.TWO.LengthAnnotation'; -export class RadiusLengthAnnotation extends DiameterDimension { +export class RadiusLengthAnnotation extends DiameterDimension implements ConstraintAnnotation<{angle: number}> { + + constraint: AlgNumConstraint; constructor(obj, constraint) { super(obj); diff --git a/web/app/sketcher/shapes/dim.js b/web/app/sketcher/shapes/dim.js index 550aae65..38c34e8a 100644 --- a/web/app/sketcher/shapes/dim.js +++ b/web/app/sketcher/shapes/dim.js @@ -7,6 +7,7 @@ import {TextHelper} from "./textHelper"; import {isInstanceOf} from "../actions/matchUtils"; import {Arc} from "./arc"; import {SketchObject} from "./sketch-object"; +import {ConstraintAnnotation} from "../constr/constraintAnnotation"; const ARROW_W_PX = 15; const ARROW_H_PX = 4; diff --git a/web/app/sketcher/viewer2d.ts b/web/app/sketcher/viewer2d.ts index 621a7c5f..ea3ba761 100644 --- a/web/app/sketcher/viewer2d.ts +++ b/web/app/sketcher/viewer2d.ts @@ -19,6 +19,7 @@ import {Styles} from './styles'; import {Dimension} from "./shapes/dim"; import {GroundObjectsGeneratorSchema} from "./generators/groundObjectsGenerator"; import {SketchGenerator} from "./generators/sketchGenerator"; +import {Generator} from "./id-generator"; export class Viewer { @@ -136,6 +137,7 @@ export class Viewer { window.removeEventListener('resize', this.onWindowResize, false); this.canvas = null; this.toolManager.dispose(); + Generator.resetIDGenerator(); }; isDisposed() {