schema from UI definition

This commit is contained in:
xibyte 2022-02-01 03:24:44 -08:00 committed by Val Erastov
parent e03a4c0f76
commit 07db5af1db
63 changed files with 905 additions and 448 deletions

View file

@ -21,7 +21,7 @@ export class OrderedMap {
}
has(key) {
this.map.has(key);
return this.map.has(key);
}
delete(key) {

View file

@ -5,15 +5,15 @@ interface Observable<T> {
interface Stream<T> extends Observable<T> {
map<T, V>(fn: (value: T) => V);
map<V>(fn: (value: T) => V);
filter<T>(stream: Stream<T>, predicate: (T) => boolean): Stream<T>;
filter(predicate: (T) => boolean): Stream<T>;
pairwise(first: T): Stream<[T, T]>;
scan(initAccumulator: any): Stream<any>;
remember(initialValue: T, usingStream: any): Stream<T>
remember(initialValue: T, usingStream: any): StateStream<T>
distinct(): Stream<T>;

14
modules/occ/occ.d.ts vendored
View file

@ -1,14 +0,0 @@
import {vectorTo_gp_Pnt} from "occ/occAdapters";
export interface OCC_Handle<T> {
get(): T;
}
export type OCC_Geom_TrimmedCurve = any;
export type OCC_Geom_Curve = any;
export type OCC_gp_Pnt = any;
export type OCC_gp_Vector = any;
export type OCC_TopoDS_Shape = any;
export type OCC_TopoDS_Edge = any;
export type OCC_TopoDS_Face = any;
export type OCC_ListOfShapes = any;

View file

@ -1,11 +0,0 @@
import {OCC_gp_Pnt, OCC_gp_Vector} from "occ/occ";
import Vector from "math/vector";
import {OCCContext} from "cad/craft/occPlugin";
export function vectorTo_gp_Pnt(oc: OCCContext, v: Vector): OCC_gp_Pnt {
return new oc.gp_Pnt_3(v.x, v.y, v.z);
}
export function vectorTo_gp_Vector(oc: OCCContext, v: Vector): OCC_gp_Vector {
return new oc.gp_Vec_4(v.x, v.y, v.z);
}

View file

@ -1,37 +0,0 @@
import {
OCC_Geom_Curve,
OCC_Geom_TrimmedCurve,
OCC_Handle, OCC_ListOfShapes,
OCC_TopoDS_Edge,
OCC_TopoDS_Face,
OCC_TopoDS_Shape
} from "occ/occ";
import {OCCContext} from "cad/craft/occPlugin";
export function upcastCurve<T extends OCC_Geom_Curve>(oc: OCCContext, curve: OCC_Handle<T>): OCC_Handle<OCC_Geom_Curve> {
return new oc.Handle_Geom_Curve_2(curve.get());
}
export function occIterateEdges(oc: OCCContext, root: OCC_TopoDS_Shape, callback: (edge: OCC_TopoDS_Edge) => any) {
const explorer = new oc.TopExp_Explorer_2(root, oc.TopAbs_ShapeEnum.TopAbs_EDGE, oc.TopAbs_ShapeEnum.TopAbs_SHAPE);
while(explorer.More()) {
callback(oc.TopoDS.Edge_1(explorer.Current()));
explorer.Next();
}
}
export function occIterateFaces(oc: OCCContext, root: OCC_TopoDS_Shape, callback: (edge: OCC_TopoDS_Face) => any) {
const explorer = new oc.TopExp_Explorer_2(root, oc.TopAbs_ShapeEnum.TopAbs_FACE, oc.TopAbs_ShapeEnum.TopAbs_SHAPE);
while(explorer.More()) {
callback(oc.TopoDS.Face_1(explorer.Current()));
explorer.Next();
}
}
export function occIterateListOfShape(oc: OCCContext, listOfShapes: OCC_ListOfShapes, callback: (edge: OCC_TopoDS_Shape) => any) {
const it = oc.TopTools_ListIteratorOfListOfShape(listOfShapes);
while (it.More()) {
callback(it.Value());
it.Next();
}
}

View file

@ -1,25 +1,27 @@
import {roundValueForPresentation as r} from 'cad/craft/operationHelper';
import {MFace} from "cad/model/mface";
import {ApplicationContext} from "context";
import {MDFCommand} from "cad/mdf/mdf";
import {EntityKind} from "cad/model/entities";
interface ExtrudeParams {
length: number;
face: MFace;
}
export default {
const ExtrudeOperation: MDFCommand<ExtrudeParams> = {
id: 'EXTRUDE',
label: 'Extrude',
icon: 'img/cad/extrude',
info: 'extrudes 2D sketch',
paramsInfo: ({value}) => `(${r(value)})`,
paramsInfo: ({length}) => `(${r(length)})`,
mutualExclusiveFields: ['datumAxisVector', 'edgeVector', 'sketchSegmentVector'],
run: (params: ExtrudeParams, ctx: ApplicationContext) => {
let occ = ctx.occService;
const oci = occ.commandInterface;
const face = ctx.cadRegistry.findFace(params.face);
const face = params.face;
let sketch = ctx.sketchStorageService.readSketch(face.id);
if (!sketch) throw 'sketch not found for the face ' + face.id;
@ -36,44 +38,26 @@ export default {
};
},
schema: {
length: {
form: [
{
type: 'number',
label: 'length',
name: 'length',
defaultValue: 50,
label: 'length'
},
face: {
type: 'face',
initializeBySelection: 0
{
type: 'selection',
name: 'face',
capture: [EntityKind.FACE],
label: 'face',
multi: false,
defaultValue: {
usePreselection: true,
preselectionIndex: 0
},
},
direction: {
type: 'direction',
optional: true
},
datumAxisVector: {
type: 'datumAxis',
optional: true,
label: 'datum axis'
},
edgeVector: {
type: 'edge',
optional: true,
label: 'edge',
accept: edge => edge.brepEdge.curve.degree === 1
},
sketchSegmentVector: {
type: 'sketchObject',
optional: true,
label: 'sketch segment',
accept: obj => obj.isSegment
},
flip: {
type: 'boolean',
defaultValue: false,
}
}
],
}
export default ExtrudeOperation;

View file

@ -1,4 +1,4 @@
import {DATUM} from '../../../web/app/cad/scene/entites';
import {DATUM} from 'cad/model/entities';
import {assertEquals, assertTrue} from '../utils/asserts';
export const TEST_MODE = 'modellerUI';

View file

@ -1,5 +1,5 @@
import {assertEquals, assertTrue} from '../utils/asserts';
import {DATUM} from '../../../web/app/cad/scene/entites';
import {DATUM} from 'cad/model/entities';
export const TEST_MODE = 'modellerUI';

View file

@ -1,6 +1,6 @@
import {MShell} from '../model/mshell';
import {MObject} from "../model/mobject";
import {ApplicationContext} from "context";
import {ApplicationContext, CoreContext} from "context";
import {Stream} from "lstream";
import {MFace} from "../model/mface";
import {MEdge} from "../model/medge";
@ -102,7 +102,7 @@ export interface CadRegistry {
}
declare module 'context' {
interface ApplicationContext {
interface CoreContext {
cadRegistry: CadRegistry;
}

View file

@ -1,11 +1,12 @@
import {addModification, stepOverriding} from './craftHistoryUtils';
import {Emitter, state, StateStream, stream} from 'lstream';
import materializeParams from './materializeParams';
import materializeParams from './schema/materializeParams';
import CadError from '../../utils/errors';
import {MObject, MObjectIdGenerator} from '../model/mobject';
import {intercept} from "lstream/intercept";
import {CoreContext} from "context";
import {MFace} from "../model/mface";
import {OperationParams} from "cad/craft/schema/schema";
export function activate(ctx: CoreContext) {
@ -229,7 +230,7 @@ function historyTravel(modifications$) {
export interface OperationRequest {
type: string;
params: any;
params: OperationParams;
}
export interface OperationResult {

View file

@ -98,7 +98,8 @@ export function getEncloseDetails(params, contours, target, csys, sketchSurface,
}
for (let i = 0; i < basePath.length; ++i) {
const curve = basePath[i];
let lidCurve = curve.translate(target);
let lidCurve = cur
ve.translate(target);
if (applyPrism) {
lidCurve = lidCurve.transform(prismTr);
}

View file

@ -11,8 +11,6 @@ export interface OCCIO {
pushModel(model: MObject, name: string);
anchorModel(name: string);
cleanupRegistry();
sketchLoader: OCCSketchLoader
@ -39,7 +37,7 @@ export function createOCCIO(oci: OCCCommandInterface): OCCIO {
return {
getShell, pushModel, anchorModel, cleanupRegistry,
getShell, pushModel, cleanupRegistry,
sketchLoader: createOCCSketchLoader(oci)
}

View file

@ -1,11 +1,21 @@
export default {
edges: {
type: 'array',
itemType: 'edge',
initializeBySelection: true
itemType: ['edge', 'face'],
max: 1,
initializeBySelection: true,
ui: {
}
},
thickness: {
type: 'number',
defaultValue: 20
defaultValue: 20,
min: 10,
max: 30,
ui: {
widget: 'slider'
}
}
}

View file

@ -1,48 +0,0 @@
import {isEntityType} from './schemaUtils';
export default function initializeBySchema(schema, context) {
let fields = Object.keys(schema);
let obj = {};
for (let field of fields ) {
let val;
let md = schema[field];
if (md.type === 'array') {
if (md.itemType === 'object') {
if (md.defaultValue) {
val = md.defaultValue;
} else if (md.initializeBySelection === true) {
let {itemField, entity} = md.defaultValue;
val = context.streams.selection[entity].value.map(s => {
let item = initializeBySchema(md.schema, context);
item[itemField] = s;
return item;
});
} else {
val = [];
}
} else if (isEntityType(md.itemType)) {
if (md.initializeBySelection === true) {
let entityContext = context.streams.selection[md.itemType];
if (entityContext) {
val = [...entityContext.value];
}
} else {
val = []
}
} else {
throw 'unsupported';
}
} else if (isEntityType(md.type) && md.initializeBySelection !== undefined) {
const entityContext = context.streams.selection[md.type];
if (entityContext) {
val = entityContext.value[md.initializeBySelection];
}
} else if (md.type === 'object') {
val = initializeBySchema(md.schema, context);
} else {
val = md.defaultValue;
}
obj[field] = val;
}
return obj;
}

View file

@ -1,94 +0,0 @@
import {isEntityType} from './schemaUtils';
export default function materializeParams(ctx, params, schema, result, errors, parentPath) {
parentPath = parentPath || ROOT_PATH;
for (let field of Object.keys(schema)) {
let md = schema[field];
if (!md) {
continue;
}
let value = params[field];
if (value === undefined || value === null || value === '') {
if (!md.optional) {
errors.push({path: [...parentPath, field], message: 'required'});
}
} else {
if (md.type === 'number') {
try {
const valueType = typeof value;
if (valueType === 'string') {
value = ctx.expressionService.evaluateExpression(value);
} else if (valueType !== 'number') {
errors.push({path: [...parentPath, field], message: 'invalid value'});
}
} catch (e) {
errors.push({path: [...parentPath, field], message: 'unable to evaluate expression'});
}
if (md.min !== undefined ) {
if (value < md.min) {
errors.push({path: [...parentPath, field], message: 'less than allowed'});
}
}
if (md.max !== undefined ) {
if (value > md.max) {
errors.push({path: [...parentPath, field], message: 'greater than allowed'});
}
}
} else if (md.type === 'string') {
if (typeof value !== 'string') {
errors.push({path: [...parentPath, field], message: 'not a string type'});
}
} else if (md.type === 'boolean') {
value = !!value;
} else if (md.type === 'enum') {
if (md.values.indexOf(value) === -1) {
value = md.defaultValue || md.values[0];
}
} else if (isEntityType(md.type)) {
if (typeof value !== 'string') {
errors.push({path: [...parentPath, field], message: 'not a valid model reference'});
}
let ref = value.trim();
if (!ref && !md.optional) {
errors.push({path: [...parentPath, field], message: 'required'});
}
let model = ctx.cadRegistry.findEntity(md.type, ref);
if (!model) {
errors.push({path: [...parentPath, field], message: 'referrers to nonexistent ' + md.type});
}
} else if (md.type === 'array') {
if (!Array.isArray(value)) {
errors.push({path: [...parentPath, field], message: 'not an array type'});
continue;
}
if (md.min !== undefined && value.length < md.min) {
errors.push({path: [...parentPath, field], message: 'required minimum ' + md.min + ' elements'});
}
if (md.max !== undefined && value.length > md.max) {
errors.push({path: [...parentPath, field], message: 'required maximum ' + md.max + ' elements'});
}
if (md.itemType === 'object') {
value = value.map((item , i) => {
let itemResult = {};
materializeParams(ctx, item, md.schema, itemResult, errors, [...parentPath, i]);
return itemResult;
});
} else {
if (isEntityType(md.itemType)) {
value.forEach(ref => {
if (!ctx.cadRegistry.findEntity(md.itemType, ref)) {
errors.push({path: [...parentPath, field], message: 'referrers to nonexistent ' + md.itemType});
}
})
}
}
}
result[field] = value;
}
}
}
const ROOT_PATH = [];

View file

@ -1,10 +1,11 @@
import React from 'react';
import {state} from 'lstream';
import {IconType} from "react-icons";
import {isEntityType} from './schemaUtils';
import {ActionAppearance} from "../actions/actionSystemPlugin";
import {ApplicationContext, CoreContext} from "context";
import {OperationResult} from "./craftPlugin";
import {OperationSchema} from "cad/craft/schema/schema";
import {FieldWidgetProps, UIDefinition} from "cad/mdf/ui/uiDefinition";
export function activate(ctx: ApplicationContext) {
@ -14,7 +15,7 @@ export function activate(ctx: ApplicationContext) {
registry:registry$
};
function addOperation(descriptor, actions) {
function addOperation(descriptor: OperationDescriptor<any>, actions) {
let {id, label, info, icon, actionParams} = descriptor;
let appearance: ActionAppearance = {
label,
@ -34,9 +35,7 @@ export function activate(ctx: ApplicationContext) {
};
actions.push(opAction);
let schemaIndex = createSchemaIndex(descriptor.schema);
registry$.mutate(registry => registry[id] = Object.assign({appearance, schemaIndex}, descriptor, {
registry$.mutate(registry => registry[id] = Object.assign({appearance}, descriptor, {
run: (request, opContext) => runOperation(request, descriptor, opContext)
}));
}
@ -87,40 +86,8 @@ export interface Operation<R> extends OperationDescriptor<R>{
icon96: string;
icon: string|IconType;
};
schemaIndex: {
entitiesByType: any;
entitiesByParam: any;
entityParams: any[];
params: any[];
},
}
function createSchemaIndex(schema) {
const entitiesByType = {};
const entitiesByParam = {};
const entityParams = [];
for (let field of Object.keys(schema)) {
let md = schema[field];
let entityType = md.type === 'array' ? md.itemType : md.type;
if (isEntityType(entityType)) {
let byType = entitiesByType[entityType];
if (!byType) {
byType = [];
entitiesByType[entityType] = byType;
}
byType.push(field);
entitiesByParam[field] = entityType;
entityParams.push(field);
}
}
return {entitiesByType, entitiesByParam,
entityParams: Object.keys(entitiesByParam),
params: Object.keys(schema)
};
}
export interface OperationDescriptor<R> {
id: string;
label: string;
@ -131,8 +98,9 @@ export interface OperationDescriptor<R> {
paramsInfo: (params: R) => string,
previewGeomProvider?: (params: R) => OperationGeometryProvider,
form: () => React.ReactNode,
schema: any,
onParamsUpdate?: (params, name, value) => void
schema: OperationSchema,
formFields: FieldWidgetProps[],
onParamsUpdate?: (params, name, value) => void,
}
export interface OperationService {

View file

@ -5,7 +5,8 @@ export default {
defaultValue: 'XY'
},
parallelTo: {
type: 'face',
type: 'entity',
allowedKinds: 'face',
optional: true,
},
depth: {

View file

@ -0,0 +1,44 @@
import {TypeRegistry, Types} from "cad/craft/schema/types";
import {OperationSchema, SchemaField} from "cad/craft/schema/schema";
import {ApplicationContext} from "context";
export default function initializeBySchema(schema: OperationSchema, context: ApplicationContext) {
let fields = Object.keys(schema);
let obj = {};
for (let field of fields) {
let val = undefined;
let md = schema[field] as SchemaField;
if (md.type === Types.array) {
if (md.items.type === Types.entity && md.items.defaultValue !== undefined) {
const defaultValue = md.items.defaultValue;
if (defaultValue.usePreselection === true) {
const entitySchema = md.items;
const currentSelection =
context.entityContextService.selectedEntities.value.filter(e => entitySchema.allowedKinds.includes(e.TYPE));
val = currentSelection.map(e => e.id);
}
} else {
val = md.defaultValue || [];
}
} else if (md.type === Types.entity && md.defaultValue !== undefined) {
const defaultValue = md.defaultValue;
if (defaultValue.usePreselection === true && defaultValue.preselectionIndex !== undefined) {
const allowedKinds = md.allowedKinds;
const currentSelection =
context.entityContextService.selectedEntities.value.filter(e => allowedKinds.includes(e.TYPE));
let mObject = currentSelection[defaultValue.preselectionIndex as number];
if (mObject) {
val = mObject.id;
}
}
} else if (md.type === Types.object) {
val = md.defaultValue || initializeBySchema(md.schema, context);
} else {
val = md.defaultValue;
}
obj[field] = val;
}
return obj;
}

View file

@ -0,0 +1,123 @@
import {TypeRegistry} from "cad/craft/schema/types";
import {CoreContext} from "context";
import {
OperationParams,
OperationParamsError,
OperationParamsErrorReporter,
OperationSchema
} from "cad/craft/schema/schema";
function createErrorReporter(path: string[], errors: OperationParamsError[]): OperationParamsErrorReporter {
function report(message: string) {
errors.push({path, message});
}
report.dot = segment => createErrorReporter([...path, segment], errors);
return report;
}
export default function materializeParams(ctx: CoreContext,
params: OperationParams,
schema: OperationSchema,
result: any,
errors: OperationParamsError[]) {
return materializeParamsImpl(ctx, params, schema, result, createErrorReporter([], errors))
}
function materializeParamsImpl(ctx: CoreContext,
params: OperationParams,
schema: OperationSchema,
result: any,
reportError: OperationParamsErrorReporter) {
for (let field of Object.keys(schema)) {
const md = schema[field];
let value = params[field];
if (value === undefined || value === null || value === '') {
if (!md.optional) {
reportError('required');
}
} else {
const typeDef = TypeRegistry[md.type];
value = typeDef.resolve(ctx, value, md as any, reportError.dot(field), materializeParamsImpl);
// if (md.type === Types.NUMBER) {
// try {
// const valueType = typeof value;
// if (valueType === 'string') {
// value = ctx.expressionService.evaluateExpression(value);
// } else if (valueType !== 'number') {
// errors.push({path, message: 'invalid value'});
// }
// } catch (e) {
// errors.push({path, message: 'unable to evaluate expression'});
// }
//
// if (md.min !== undefined ) {
// if (value < md.min) {
// errors.push({path, message: 'less than allowed'});
// }
// }
// if (md.max !== undefined ) {
// if (value > md.max) {
// errors.push({path, message: 'greater than allowed'});
// }
// }
// } else if (md.type === Types.STRING) {
// if (typeof value !== 'string') {
// errors.push({path, message: 'not a string type'});
// }
// } else if (md.type === Types.BOOLEAN) {
// value = !!value;
// } else if (md.type === Types.ENUM) {
// if (md.values.indexOf(value) === -1) {
// value = md.defaultValue || md.values[0];
// }
// } else if (isEntityType(md.type)) {
// if (typeof value !== 'string') {
// errors.push({path, message: 'not a valid model reference'});
// }
// let ref = value.trim();
// if (!ref && !md.optional) {
// errors.push({path, message: 'required'});
// }
// let model = ctx.cadRegistry.find(ref);
// if (!model) {
// errors.push({path, message: 'referrers to nonexistent ' + md.type});
// }
// value = model;
// } else if (md.type === Types.ARRAY) {
// if (!Array.isArray(value)) {
// errors.push({path, message: 'not an array type'});
// continue;
// }
// if (md.min !== undefined && value.length < md.min) {
// errors.push({path, message: 'required minimum ' + md.min + ' elements'});
// }
// if (md.max !== undefined && value.length > md.max) {
// errors.push({path, message: 'required maximum ' + md.max + ' elements'});
// }
// if (md.itemType === Types.OBJECT) {
// value = value.map((item , i) => {
// let itemResult = {};
// materializeParams(ctx, item, md.schema, itemResult, errors, [...parentPath, i]);
// return itemResult;
// });
// } else {
// if (isEntityType(md.itemType)) {
// value.forEach(ref => {
// if (!ctx.cadRegistry.findEntity(md.itemType, ref)) {
// errors.push({path, message: 'referrers to nonexistent ' + md.itemType});
// }
// })
// }
// }
// }
result[field] = value;
}
}
}

View file

@ -0,0 +1,33 @@
import {Types} from "cad/craft/schema/types";
import {NumberTypeSchema} from "cad/craft/schema/types/numberType";
import {EntityTypeSchema} from "cad/craft/schema/types/entityType";
import {ArrayTypeSchema} from "cad/craft/schema/types/arrayType";
import {ObjectTypeSchema} from "cad/craft/schema/types/objectType";
import {EnumTypeSchema} from "cad/craft/schema/types/enumType";
export type SchemaField = NumberTypeSchema | EntityTypeSchema | ArrayTypeSchema | ObjectTypeSchema | EnumTypeSchema;
export type OperationSchema = {
[key: string]: SchemaField;
};
export interface BaseSchemaField {
defaultValue: Coercable,
optional: boolean,
label?: string
}
export type Coercable = any;
export type OperationParams = {
[key: string]: Coercable
}
export type OperationParamsError = {
path: string[],
message: string
};
export type OperationParamsErrorReporter = ((msg: string) => void) & {
dot: (pathPart: string|number) => OperationParamsErrorReporter
};

View file

@ -0,0 +1,39 @@
import {Materializer, Type, TypeRegistry, Types} from "cad/craft/schema/types/index";
import {CoreContext} from "context";
import {BaseSchemaField, OperationParamsErrorReporter, SchemaField} from "cad/craft/schema/schema";
export interface ArrayTypeSchema extends BaseSchemaField {
type: Types.array;
items: SchemaField;
min: number;
max: number;
}
export const ArrayType: Type<any[], any[], ArrayTypeSchema> = {
resolve(ctx: CoreContext,
value: any[],
md: ArrayTypeSchema,
reportError: OperationParamsErrorReporter,
materializer: Materializer): any[] {
if (!value || !Array.isArray(value)) {
reportError('not an array type');
return [];
}
if (md.min !== undefined && value.length < md.min) {
reportError('required minimum ' + md.min + ' elements');
}
if (md.max !== undefined && value.length > md.max) {
reportError('required maximum ' + md.max + ' elements');
}
const itemType = TypeRegistry[md.items.type];
return value.map((v, i) => itemType.resolve(ctx, v, md.items as any, reportError.dot(i), materializer));
}
}

View file

@ -0,0 +1,37 @@
import {Type, TypeRegistry, Types} from "cad/craft/schema/types/index";
import {CoreContext} from "context";
import {BaseSchemaField, OperationParamsErrorReporter} from "cad/craft/schema/schema";
import {EntityKind} from "cad/model/entities";
import {MObject} from "cad/model/mobject";
export interface EntityTypeSchema extends BaseSchemaField {
type: Types.entity,
allowedKinds: EntityKind[];
initializeBySelection: boolean | number;
}
export const EntityType: Type<string, MObject, EntityTypeSchema> = {
resolve(ctx: CoreContext,
value: string,
md: EntityTypeSchema,
reportError: OperationParamsErrorReporter): MObject {
if (typeof value !== 'string') {
reportError('not a valid model reference');
}
let ref = value.trim();
if (!ref && !md.optional) {
reportError('required');
}
let model = ctx.cadRegistry.find(ref);
if (!model) {
reportError('refers to a nonexistent object');
}
return model;
}
}

View file

@ -0,0 +1,26 @@
import {Materializer, Type, TypeRegistry, Types} from "cad/craft/schema/types/index";
import {CoreContext} from "context";
import {BaseSchemaField, OperationParamsErrorReporter} from "cad/craft/schema/schema";
export interface EnumTypeSchema extends BaseSchemaField {
type: Types.number,
values: string[]
}
export const EnumType: Type<any, number, EnumTypeSchema> = {
resolve(ctx: CoreContext,
value: any,
md: EnumTypeSchema,
reportError: OperationParamsErrorReporter,
materializer: Materializer): number {
if (md.values.indexOf(value) === -1) {
value = md.defaultValue || md.values[0];
}
return value;
}
}

View file

@ -0,0 +1,37 @@
import {CoreContext} from "context";
import {OperationParams, OperationParamsErrorReporter, OperationSchema, SchemaField} from "cad/craft/schema/schema";
import {ArrayType} from "cad/craft/schema/types/arrayType";
import {EntityType} from "cad/craft/schema/types/entityType";
import {NumberType} from "cad/craft/schema/types/numberType";
import {ObjectType} from "cad/craft/schema/types/objectType";
import {EnumType} from "cad/craft/schema/types/enumType";
export type Materializer = (ctx: CoreContext,
params: OperationParams,
schema: OperationSchema,
result: any,
reportError: OperationParamsErrorReporter) => void;
export interface Type<IN, OUT, METADATA extends SchemaField> {
resolve(ctx: CoreContext, value: IN, md: METADATA,
reportError: OperationParamsErrorReporter,
materializer: Materializer): OUT;
}
export enum Types {
array = 'array',
entity = 'entity',
number = 'number',
object = 'object',
enum = 'enum',
}
export const TypeRegistry = {
[Types.array]: ArrayType,
[Types.entity]: EntityType,
[Types.number]: NumberType,
[Types.object]: ObjectType,
[Types.enum]: EnumType,
};

View file

@ -0,0 +1,47 @@
import {Materializer, Type, TypeRegistry, Types} from "cad/craft/schema/types/index";
import {CoreContext} from "context";
import {BaseSchemaField, OperationParamsErrorReporter, SchemaField} from "cad/craft/schema/schema";
import {EntityType} from "cad/craft/schema/types/entityType";
export interface NumberTypeSchema extends BaseSchemaField {
type: Types.number,
min: number;
max: number;
}
export const NumberType: Type<any, number, NumberTypeSchema> = {
resolve(ctx: CoreContext,
value: any,
md: NumberTypeSchema,
reportError: OperationParamsErrorReporter,
materializer: Materializer): number {
try {
const valueType = typeof value;
if (valueType === 'string') {
value = ctx.expressionService.evaluateExpression(value);
} else if (valueType !== 'number') {
reportError('invalid value');
}
} catch (e) {
reportError('unable to evaluate expression');
}
if (md.min !== undefined) {
if (value < md.min) {
reportError('less than allowed');
}
}
if (md.max !== undefined) {
if (value > md.max) {
reportError('greater than allowed');
}
}
return value;
}
}

View file

@ -0,0 +1,25 @@
import {Materializer, Type, TypeRegistry, Types} from "cad/craft/schema/types/index";
import {CoreContext} from "context";
import {BaseSchemaField, OperationParamsErrorReporter, OperationSchema} from "cad/craft/schema/schema";
export interface ObjectTypeSchema extends BaseSchemaField {
type: Types.object;
schema: OperationSchema;
}
export const ObjectType: Type<any, any, ObjectTypeSchema> = {
resolve(ctx: CoreContext,
value: any,
md: ObjectTypeSchema,
reportError: OperationParamsErrorReporter,
materializer: Materializer): any {
const result = {};
materializer(ctx, value, md.schema, result, reportError);
return result;
}
}

View file

@ -1,5 +0,0 @@
import {ENTITIES} from '../scene/entites';
export function isEntityType(type) {
return ENTITIES.indexOf(type) !== -1
}

View file

@ -4,7 +4,7 @@ import Stack from 'ui/components/Stack';
import {FormContext} from '../form/Form';
import mapContext from 'ui/mapContext';
import PropTypes from 'prop-types';
import initializeBySchema from '../../../intializeBySchema';
import initializeBySchema from '../../../schema/initializeBySchema';
@attachToForm
@mapContext(({streams}) => ({streams}))

View file

@ -1,7 +1,7 @@
import {state} from 'lstream';
import initializeBySchema from '../intializeBySchema';
import initializeBySchema from '../schema/initializeBySchema';
import {clone, EMPTY_OBJECT} from 'gems/objects';
import materializeParams from '../materializeParams';
import materializeParams from '../schema/materializeParams';
import {createFunctionList} from 'gems/func';
import {onParamsUpdate} from '../cutExtrude/extrudeOperation';
import {propsChangeTracker} from 'lstream/utils';

View file

@ -1,4 +1,5 @@
import {DATUM, DATUM_AXIS, EDGE, FACE, LOOP, SHELL, SKETCH_OBJECT} from '../../scene/entites';
import {FACE, SHELL} from '../../model/entities';
import {memoize} from "lodash/function";
export function activate(ctx) {
ctx.streams.wizard.wizardContext.attach(wizCtx => {
@ -9,16 +10,22 @@ export function activate(ctx) {
wizCtx.workingRequest$.attach(({type, params}) => {
const marker = ctx.services.marker;
marker.startSession();
let {schema, schemaIndex} = wizCtx.operation;
schemaIndex.entityParams.forEach(param => {
let color = schema[param].markColor;
let {schema} = wizCtx.operation;
Object.keys(schema).forEach(param => {
let md = schema[param];
if (md.type !== 'entity') {
return;
}
//TODO: move to uiDefinition
let color = md.markColor;
let val = params[param];
let entity = schemaIndex.entitiesByParam[param];
if (Array.isArray(val)) {
val.forEach(id => marker.mark(entity, id, color));
val.forEach(id => marker.mark(id, color));
} else {
if (val) {
marker.mark(entity, val, color);
marker.mark(val, color);
}
}
});
@ -42,7 +49,10 @@ const arrayValue = (id, arr) => {
return arr;
};
const getEntityParams = memoize(schema => Object.keys(schema).filter(key => schema[key].type === 'entity'));
function createPickHandlerFromSchema(wizCtx) {
function update(param, value, paramToMakeActive) {
wizCtx.updateParam(param, value);
wizCtx.updateState(state => {
@ -55,44 +65,44 @@ function createPickHandlerFromSchema(wizCtx) {
const params = wizCtx.workingRequest$.value.params;
const state = wizCtx.state$.value;
let {schema, schemaIndex} = wizCtx.operation;
const {entitiesByType, entitiesByParam, entityParams} = schemaIndex;
let {schema} = wizCtx.operation;
const activeMd = state.activeParam && schema[state.activeParam];
const activeEntity = state.activeParam && entitiesByParam[state.activeParam];
const activeCanTakeIt = kind => activeMd.allowedKinds && activeMd.allowedKinds.includes(kind);
function select(param, entity, md, id) {
function select(param, md, id) {
const valueGetter = md.type === 'array' ? arrayValue : singleValue;
let paramToMakeActive = getNextActiveParam(param, entity, md);
let paramToMakeActive = getNextActiveParam(param, md);
update(param, valueGetter(id, params[param]), paramToMakeActive);
}
function getNextActiveParam(currParam, entity, currMd) {
function getNextActiveParam(currParam, currMd) {
if (currMd.type !== 'array') {
let entityGroup = entitiesByType[entity];
if (entityGroup) {
const index = entityGroup.indexOf(currParam);
const nextIndex = (index + 1) % entityGroup.length;
return entityGroup[nextIndex];
}
const entityParams = getEntityParams(schema);
const index = entityParams.indexOf(currParam);
const nextIndex = (index + 1) % entityParams.length;
return entityParams[nextIndex];
}
return currParam;
}
function selectActive(id) {
select(state.activeParam, activeEntity, activeMd, id);
select(state.activeParam, activeMd, id);
}
function selectToFirst(entity, id) {
let entities = entitiesByType[entity];
if (!entities) {
return false;
const entityParams = getEntityParams(schema);
for (let param of entityParams) {
const md = schema[param];
if (md.allowedKinds.includes(entity)) {
select(param, md, id);
}
}
let param = entities[0];
select(param, entity, schema[param], id);
return true;
}
function deselectIfNeeded(id) {
const entityParams = getEntityParams(schema);
for (let param of entityParams) {
let val = params[param];
if (val === id) {
@ -117,44 +127,20 @@ function createPickHandlerFromSchema(wizCtx) {
}
if (modelType === FACE) {
if (activeEntity === SHELL) {
if (activeCanTakeIt(SHELL)) {
selectActive(model.shell.id);
} else if (activeEntity === FACE) {
} else if (activeCanTakeIt(FACE)) {
selectActive(model.id);
} else {
if (!selectToFirst(FACE, model.id)) {
selectToFirst(SHELL, model.shell.id)
}
}
} else if (modelType === SKETCH_OBJECT) {
if (activeEntity === SKETCH_OBJECT) {
} else{
if (activeCanTakeIt(modelType)) {
selectActive(model.id);
} else {
selectToFirst(SKETCH_OBJECT, model.id);
}
} else if (modelType === EDGE) {
if (activeEntity === EDGE) {
selectActive(model.id);
} else {
selectToFirst(EDGE, model.id);
}
} else if (modelType === DATUM) {
if (activeEntity === DATUM) {
selectActive(model.id);
} else {
selectToFirst(DATUM, model.id);
}
} else if (modelType === DATUM_AXIS) {
if (activeEntity === DATUM_AXIS) {
selectActive(model.id);
} else {
selectToFirst(DATUM_AXIS, model.id);
}
} else if (modelType === LOOP) {
if (activeEntity === LOOP) {
selectActive(model.id);
} else {
selectToFirst(LOOP, model.id);
selectToFirst(modelType, model.id);
}
}
return false;

View file

@ -1,41 +1,39 @@
import { IconDeclaration } from "cad/icons/IconDeclaration";
import { CoreContext } from "context";
import { IconType } from "react-icons";
import { OperationResult } from "../craft/craftPlugin";
import { OperationDescriptor } from "../craft/operationPlugin";
import { generateForm } from "./generateForm";
import { resolveMDFIcon } from "./mdfIconResolver";
import {IconDeclaration} from "cad/icons/IconDeclaration";
import {CoreContext} from "context";
import {IconType} from "react-icons";
import {OperationResult} from "../craft/craftPlugin";
import {OperationDescriptor} from "../craft/operationPlugin";
import {resolveMDFIcon} from "./mdfIconResolver";
import {OperationSchema} from "cad/craft/schema/schema";
import {
DynamicWidgetProps,
FieldWidgetProps, FormDefinition,
isContainerWidgetProps,
isFieldWidgetProps,
UIDefinition
} from "cad/mdf/ui/uiDefinition";
import {uiDefinitionToReact} from "cad/mdf/ui/render";
import {DynamicComponents} from "cad/mdf/ui/componentRegistry";
interface MDFCommand<R> {
export interface MDFCommand<R> {
id: string;
label: string;
info: string;
icon: IconType | IconDeclaration;
run: (request: R, opContext: CoreContext) => OperationResult | Promise<OperationResult>;
paramsInfo: (params: R) => string,
schema: OperationSchema,
mutualExclusiveFields?: string[]
mutualExclusiveFields?: string[],
form: FormDefinition
}
export type Coercable = any;
export type OperationSchema = {
[key: string]: SchemaField
};
export interface SchemaField {
type: 'number' | 'boolean' | 'string' | 'face' | 'datumAxis' | 'edge' | 'sketchObject',
enum?: {
value: string;
label: string;
}[];
defaultValue: Coercable,
optional: boolean,
label?: string
}
export function loadMDFCommand<R>(mdfCommand: MDFCommand<R>): OperationDescriptor<R> {
const uiDefinition: UIDefinition = {
type: 'group',
content: mdfCommand.form
}
const formFields = extractFormFields(uiDefinition);
const derivedSchema = deriveSchema(formFields);
return {
id: mdfCommand.id,
label: mdfCommand.label,
@ -51,11 +49,42 @@ export function loadMDFCommand<R>(mdfCommand: MDFCommand<R>): OperationDescripto
// actionParams: {
// ...requiresFaceSelection(1)
// },
form: generateForm(mdfCommand.schema),
schema: mdfCommand.schema
form: uiDefinitionToReact(uiDefinition),
formFields,
schema: derivedSchema
}
}
function extractFormFields(uiDefinition: UIDefinition): FieldWidgetProps[] {
const fields: FieldWidgetProps[] = [];
function inorder(comp: DynamicWidgetProps) {
if (isFieldWidgetProps(comp)) {
fields.push(comp);
}
if (isContainerWidgetProps(comp)) {
comp.content.forEach(inorder)
}
}
inorder(uiDefinition);
return fields;
}
export function deriveSchema(formFields: FieldWidgetProps[]): OperationSchema {
const schema = {};
formFields.forEach(f => {
let propsToSchema = DynamicComponents[f.type].propsToSchema;
schema[f.name] = propsToSchema(schema, f as any);
});
return schema;
}
export function handleMutualExclusiveFields(mutualExclusiveFields, params, name, value) {
if (mutualExclusiveFields.includes(name)) {
mutualExclusiveFields.forEach(param => {

View file

@ -0,0 +1,15 @@
import React from "react";
import {ContainerBasicProps, ContainerWidgetProps} from "cad/mdf/ui/ContainerWidget";
export interface AccordionWidgetProps extends ContainerBasicProps {
type: 'accordion';
}
export function AccordionWidget(props: AccordionWidgetProps) {
return "TBD"
}

View file

@ -0,0 +1,26 @@
import React from 'react';
import {DynamicWidgetProps} from "cad/mdf/ui/uiDefinition";
import {DynamicComponentWidget} from "cad/mdf/ui/DynamicComponentWidget";
export interface ContainerBasicProps {
content: DynamicWidgetProps[];
}
export interface ContainerWidgetProps extends ContainerBasicProps {
type: 'container',
}
export function ContainerWidget({content}: ContainerBasicProps) {
return <React.Fragment>{content.map((comp, i) => <DynamicComponentWidget key={i} {...comp} />)}</React.Fragment>;
}

View file

@ -0,0 +1,43 @@
import React from 'react';
import {DynamicComponents} from "cad/mdf/ui/componentRegistry";
import {DynamicWidgetProps} from "cad/mdf/ui/uiDefinition";
export function DynamicComponentWidget(props: DynamicWidgetProps) {
const ToRender = DynamicComponents[props.type];
if (!ToRender) {
return <span>Unknown component: {props.type}</span>
}
return <ToRender {...props}/>
// return function DynamicUI() {
// return <Group>
// {Object.keys(schema).map(key => {
//
// const fieldDef: SchemaField = schema[key];
// const label = fieldDef.label || key;
//
// if (fieldDef.type === 'number') {
// return <NumberField name={key} defaultValue={fieldDef.defaultValue} label={label} />
// } else if (fieldDef.type === 'string') {
// if (fieldDef.enum) {
// return <ComboBoxField name={key} label={label}>
// {fieldDef.enum.map(opt => <ComboBoxOption key={opt.value} value={opt.value}>
// {opt.label}
// </ComboBoxOption>)}
// </ComboBoxField>
// } else {
// return <TextField name={key} label={label} />;
// }
// } else if (['face', 'edge', 'sketchObject', 'datumAxis'].includes(fieldDef.type)) {
// return <Entity name={key} label={label} />;
// } else if (fieldDef.type === 'boolean') {
// return <CheckboxField name={key} label={label} />;
// } else {
// return "I don't know";
// }
//
// })}
// </Group>;
// };
}

View file

@ -0,0 +1,22 @@
import React from 'react';
import {ContainerBasicProps, ContainerWidget} from "cad/mdf/ui/ContainerWidget";
import {Group} from "cad/craft/wizard/components/form/Form";
export interface GroupWidgetProps extends ContainerBasicProps {
type: 'group',
}
export function GroupWidget({content}: GroupWidgetProps) {
return <Group>
<ContainerWidget content={content} />
</Group>
}

View file

@ -0,0 +1,31 @@
import {NumberField} from "cad/craft/wizard/components/form/Fields";
import React from "react";
import {OperationSchema} from "cad/craft/schema/schema";
import {FieldBasicProps, fieldToSchemaGeneric} from "cad/mdf/ui/field";
import {Types} from "cad/craft/schema/types";
export interface NumberWidgetProps extends FieldBasicProps {
type: 'number';
style?: 'slider' | 'default';
min?: number;
max?: number;
}
export function NumberWidget(props: NumberWidgetProps) {
return <NumberField name={props.name} defaultValue={props.defaultValue} label={props.label} />
}
NumberWidget.propsToSchema = (consumer: OperationSchema, props: NumberWidgetProps) => {
return {
type: Types.number,
min: props.min,
max: props.max,
...fieldToSchemaGeneric(props),
}
};

View file

@ -0,0 +1,50 @@
import React from "react";
import Entity from "cad/craft/wizard/components/form/EntityList";
import {EntityType} from "cad/craft/schema/types/entityType";
import {OperationSchema, SchemaField} from "cad/craft/schema/schema";
import {FieldBasicProps, fieldToSchemaGeneric} from "cad/mdf/ui/field";
import {EntityKind} from "cad/model/entities";
import {ArrayType, ArrayTypeSchema} from "cad/craft/schema/types/arrayType";
import {Types} from "cad/craft/schema/types";
export interface SelectionWidgetProps extends FieldBasicProps {
type: 'selection';
multi?: boolean;
capture: EntityKind[];
min?: number;
max?: number;
}
export function SelectionWidget(props: SelectionWidgetProps) {
return <Entity name={props.name} label={props.label} />;
}
SelectionWidget.propsToSchema = (consumer: OperationSchema, props: SelectionWidgetProps) => {
let value = {
type: Types.entity,
allowedKinds: props.capture,
initializeBySelection: true,
...fieldToSchemaGeneric(props),
} as SchemaField;
if (props.multi) {
const items = value;
value = {
type: Types.array,
min: props.min,
max: props.max,
items
} as ArrayTypeSchema;
}
return value;
};

View file

@ -0,0 +1,15 @@
import {NumberWidget} from "cad/mdf/ui/NumberWidget";
import {SelectionWidget} from "cad/mdf/ui/SelectionWidget";
import {ContainerWidget} from "cad/mdf/ui/ContainerWidget";
import {GroupWidget} from "cad/mdf/ui/GroupWidget";
export const DynamicComponents = {
'number': NumberWidget,
'selection': SelectionWidget,
'container': ContainerWidget,
'group': GroupWidget,
}

View file

@ -0,0 +1,20 @@
import {Coercable} from "cad/craft/schema/schema";
export interface FieldBasicProps {
name: string;
label?: string;
defaultValue?: Coercable;
optional?: boolean
}
export function fieldToSchemaGeneric(props: FieldBasicProps) {
return {
label: props.label,
defaultValue: props.defaultValue,
optional: !!props.optional,
}
}

View file

@ -0,0 +1,7 @@
import React from 'react';
import {DynamicComponentWidget} from "cad/mdf/ui/DynamicComponentWidget";
import {UIDefinition} from "cad/mdf/ui/uiDefinition";
export function uiDefinitionToReact(uiDefinition: UIDefinition) {
return () => <DynamicComponentWidget {...uiDefinition} />;
}

View file

@ -0,0 +1,24 @@
import {NumberWidgetProps} from "cad/mdf/ui/NumberWidget";
import {SelectionWidgetProps} from "cad/mdf/ui/SelectionWidget";
import {AccordionWidgetProps} from "cad/mdf/ui/AccordionWidget";
import {DynamicComponents} from "cad/mdf/ui/componentRegistry";
import {ContainerWidgetProps} from "cad/mdf/ui/ContainerWidget";
export type FieldWidgetProps = NumberWidgetProps | SelectionWidgetProps;
export type BasicWidgetProps = ContainerWidgetProps | AccordionWidgetProps;
export type DynamicWidgetProps = FieldWidgetProps | BasicWidgetProps;
export type UIDefinition = DynamicWidgetProps;
export type FormDefinition = DynamicWidgetProps[];
export function isContainerWidgetProps(comp: DynamicWidgetProps): comp is ContainerWidgetProps {
return (comp as ContainerWidgetProps).content !== undefined;
}
export function isFieldWidgetProps(comp: DynamicWidgetProps): comp is FieldWidgetProps {
return DynamicComponents[comp.type].propsToSchema !== undefined;
}

View file

@ -0,0 +1,22 @@
export enum EntityKind {
SHELL = 'shell',
FACE = 'face',
EDGE = 'edge',
VERTEX = 'vertex',
SKETCH_OBJECT = 'sketchObject',
DATUM = 'datum',
DATUM_AXIS = 'datumAxis',
LOOP = 'loop'
}
//Backward comp.
export const SHELL = EntityKind.SHELL;
export const FACE = EntityKind.FACE;
export const EDGE = EntityKind.EDGE;
export const VERTEX = EntityKind.VERTEX;
export const SKETCH_OBJECT = EntityKind.SKETCH_OBJECT;
export const DATUM = EntityKind.DATUM;
export const DATUM_AXIS = EntityKind.DATUM_AXIS;
export const LOOP = EntityKind.LOOP;

View file

@ -1,11 +1,11 @@
import {MObject, MObjectIdGenerator, MRootObject} from './mobject';
import {MObject, MObjectIdGenerator} from './mobject';
import CSys from "math/csys";
import Vector from "math/vector";
import {Matrix3x4} from "math/matrix";
import {EntityKind} from "cad/model/entities";
export class MDatum extends MObject {
static TYPE = 'datum';
static TYPE = EntityKind.DATUM;
csys: CSys;
xAxis: MDatumAxis;
yAxis: MDatumAxis;
@ -42,7 +42,7 @@ export class MDatum extends MObject {
export class MDatumAxis extends MObject {
static TYPE = 'datumAxis';
static TYPE = EntityKind.DATUM_AXIS;
origin: Vector;
dir: Vector;
holder: MObject;

View file

@ -1,9 +1,10 @@
import {MObject} from './mobject';
import {MBrepShell} from "./mshell";
import {EntityKind} from "cad/model/entities";
export class MEdge extends MObject {
static TYPE = 'edge';
static TYPE = EntityKind.EDGE;
shell: MBrepShell;
brepEdge: any;

View file

@ -9,10 +9,11 @@ import {MBrepShell, MShell} from "./mshell";
import BBox from "math/bbox";
import {Basis, BasisForPlane} from "math/basis";
import {Face} from "brep/topo/face";
import {EntityKind} from "cad/model/entities";
export class MFace extends MObject {
static TYPE = 'face';
static TYPE = EntityKind.FACE;
shell: MShell;
surface: any;
sketchObjects: MSketchObject[];

View file

@ -1,10 +1,11 @@
import {MObject} from './mobject';
import {MFace} from "./mface";
import {MSketchObject} from "./msketchObject";
import {EntityKind} from "cad/model/entities";
export class MLoop extends MObject {
static TYPE = 'loop';
static TYPE = EntityKind.LOOP;
constructor(id) {
super(MLoop.TYPE, id);

View file

@ -1,13 +1,14 @@
import {IDENTITY_MATRIX, Matrix3x4} from "math/matrix";
import {EntityKind} from "cad/model/entities";
export abstract class MObject {
TYPE: string;
TYPE: EntityKind;
id: string;
ext: any = {};
constructor(TYPE, id) {
protected constructor(TYPE, id) {
this.TYPE = TYPE;
this.id = id;
}

View file

@ -7,10 +7,11 @@ import {state, StateStream} from "lstream";
import {Matrix3x4} from "math/matrix";
import {Shell} from "brep/topo/shell";
import {TopoObject} from "brep/topo/topo-object";
import {EntityKind} from "cad/model/entities";
export class MShell extends MObject {
static TYPE = 'shell';
static TYPE = EntityKind.SHELL;
csys: CSys;

View file

@ -1,9 +1,10 @@
import {MObject} from './mobject';
import {MFace} from "./mface";
import {EntityKind} from "cad/model/entities";
export class MSketchObject extends MObject {
static TYPE = 'sketchObject';
static TYPE = EntityKind.SKETCH_OBJECT;
face: MFace;
sketchPrimitive: any;
construction: boolean;

View file

@ -1,9 +1,10 @@
import {MObject} from './mobject';
import {MShell} from "./mshell";
import {EntityKind} from "cad/model/entities";
export class MVertex extends MObject {
static TYPE = 'vertex';
static TYPE = EntityKind.VERTEX;
shell: MShell;
brepVertex: any;

View file

@ -1,5 +1,5 @@
import {state} from 'lstream';
import {DATUM, EDGE, FACE, SHELL, SKETCH_OBJECT} from '../entites';
import {DATUM, EDGE, FACE, SHELL, SKETCH_OBJECT} from '../../model/entities';
const SELECTABLE_ENTITIES = [FACE, EDGE, SKETCH_OBJECT, DATUM, SHELL];

View file

@ -1,6 +1,6 @@
import * as mask from 'gems/mask'
import {getAttribute, setAttribute} from 'scene/objectData';
import {FACE, EDGE, SKETCH_OBJECT, DATUM, SHELL, DATUM_AXIS, LOOP} from '../entites';
import {FACE, EDGE, SKETCH_OBJECT, DATUM, SHELL, DATUM_AXIS, LOOP} from '../../model/entities';
import {LOG_FLAGS} from '../../logFlags';
import * as vec from 'math/vec';
import {initRayCastDebug, printRaycastDebugInfo, RayCastDebugInfo} from "./rayCastDebug";

View file

@ -1,21 +0,0 @@
import {MShell} from '../model/mshell';
import {MFace} from '../model/mface';
import {MEdge} from '../model/medge';
import {MVertex} from '../model/mvertex';
import {MSketchObject} from '../model/msketchObject';
import {MDatum, MDatumAxis} from '../model/mdatum';
import {MLoop} from '../model/mloop';
export const SHELL = MShell.TYPE;
export const FACE = MFace.TYPE;
export const EDGE = MEdge.TYPE;
export const VERTEX = MVertex.TYPE;
export const SKETCH_OBJECT = MSketchObject.TYPE;
export const DATUM = MDatum.TYPE;
export const DATUM_AXIS = MDatumAxis.TYPE;
export const LOOP = MLoop.TYPE;
export const ENTITIES = [SHELL, DATUM, FACE, EDGE, VERTEX, SKETCH_OBJECT, DATUM_AXIS, LOOP];
export const PART_MODELING_ENTITIES = [SHELL, FACE, EDGE, VERTEX, SKETCH_OBJECT];
export const ASSEMBLY_ENTITIES = [SHELL, FACE, EDGE, VERTEX];

View file

@ -1,9 +1,10 @@
import {state} from 'lstream';
import {state, StateStream} from 'lstream';
import {addToListInMap} from 'gems/iterables';
import {EMPTY_ARRAY} from 'gems/iterables';
import {DATUM, FACE, SHELL, SKETCH_OBJECT, EDGE, LOOP} from './entites';
import {DATUM, FACE, SHELL, SKETCH_OBJECT, EDGE, LOOP} from '../model/entities';
import {combine} from "lstream";
import {MObject} from "cad/model/mobject";
export const SELECTABLE_ENTITIES = [FACE, EDGE, SKETCH_OBJECT, DATUM, SHELL];
@ -12,7 +13,8 @@ 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();
ctx.streams.selection.all = combine(...(Object.values(ctx.streams.selection) as StateStream<string[]>[]))
.map(selection => [].concat(...selection)).throttle();
}
export function activate(ctx) {
@ -20,8 +22,9 @@ export function activate(ctx) {
SELECTABLE_ENTITIES.forEach(entity => {
let entitySelectApi = {
objects: [],
single: undefined
};
single: undefined,
select: null as (ids: string[]) => void
};
ctx.services.selection[entity] = entitySelectApi;
let selectionState = ctx.streams.selection[entity];
@ -53,4 +56,17 @@ export function activate(ctx) {
}
});
})
}
ctx.entityContextService = {
selectedEntities: ctx.streams.selection.all.map(ids => ids.map(ctx.cadRegistry.find)).remember()
}
}
declare module 'context' {
interface CoreContext {
entityContextService: {
selectedEntities: StateStream<MObject[]>
};
}
}

View file

@ -2,7 +2,7 @@ import {OrderedMap} from 'gems/linkedMap';
import {eventStream} from 'lstream';
export function activate(ctx) {
ctx.services.marker = createMarker(ctx.services.cadRegistry.findEntity, ctx.services.viewer.requestRender);
ctx.services.marker = createMarker(ctx.services.cadRegistry.find, ctx.services.viewer.requestRender);
ctx.streams.craft.models.attach(() => {
ctx.services.marker.clear();
});
@ -19,8 +19,8 @@ function createMarker(findEntity, requestRender) {
const notify = () => $markedEntities.next(marked);
const isMarked = id => marked.has(id);
function doMark(entity, id, color) {
let mObj = findEntity(entity, id);
function doMark(id, color) {
let mObj = findEntity(id);
if (!mObj) {
console.warn('no entity found to highlight: ' + entity + ' ' + id);
return;
@ -57,19 +57,19 @@ function createMarker(findEntity, requestRender) {
function markExclusively(entity, id, color) {
withdrawAllOfType(entity);
doMark(entity, id, color);
doMark(id, color);
onUpdate();
}
function markArrayExclusively(entity, ids, color) {
withdrawAllOfType(entity);
ids.forEach(id => doMark(entity, id, color));
ids.forEach(id => doMark(id, color));
onUpdate();
}
function markAdding(entity, id, color) {
if (!marked.has(id)) {
doMark(entity, id, color);
doMark(id, color);
onUpdate();
}
}
@ -81,13 +81,13 @@ function createMarker(findEntity, requestRender) {
needUpdate = false;
}
function mark(entity, id, color) {
function mark(id, color) {
if (!sessionInProgress) {
throw 'can be called only withing a session';
}
markingSession.add(id);
if (!marked.has(id)) {
doMark(entity, id, color);
doMark(id, color);
needUpdate = true;
}
}

View file

@ -1,6 +1,6 @@
import {View} from './view';
import DatumObject3D from '../../craft/datum/datumObject';
import {DATUM, DATUM_AXIS} from '../entites';
import {DATUM, DATUM_AXIS} from '../../model/entities';
import {setAttribute} from 'scene/objectData';
import {Mesh, MeshBasicMaterial, PolyhedronGeometry, SphereGeometry} from 'three';
import {CSYS_SIZE_MODEL} from '../../craft/datum/csysObject';

View file

@ -1,6 +1,6 @@
import {setAttribute} from 'scene/objectData';
import {brepFaceToGeom, tessDataToGeom} from '../wrappers/brepSceneObject';
import {FACE} from '../entites';
import {FACE} from '../../model/entities';
import * as SceneGraph from 'scene/sceneGraph';
import {SketchObjectView} from './sketchObjectView';
import {View} from './view';

View file

@ -1,5 +1,5 @@
import {setAttribute} from 'scene/objectData';
import {FACE, SHELL} from '../entites';
import {FACE, SHELL} from '../../model/entities';
import {NULL_COLOR, SELECTION_COLOR, setFacesColor, SketchingView} from './faceView';
import {View} from './view';
import {SketchMesh} from './shellView';

View file

@ -4,7 +4,7 @@ import {getAttribute, setAttribute} from 'scene/objectData';
import {createSolidMaterial} from '../wrappers/sceneObject';
import {FaceView, SELECTION_COLOR} from './faceView';
import {EdgeView} from './edgeView';
import {FACE, SHELL} from '../entites';
import {FACE, SHELL} from '../../model/entities';
import {Mesh} from 'three';
import {VertexView} from "./vertexView";

View file

@ -6,7 +6,7 @@ import {DoubleSide, Geometry, Mesh} from 'three';
import {surfaceAndPolygonsToGeom} from '../wrappers/brepSceneObject';
import {TriangulatePolygons} from '../../tess/triangulation';
import Vector from 'math/vector';
import {LOOP} from '../entites';
import {LOOP} from '../../model/entities';
import {setAttribute} from 'scene/objectData';
export class SketchLoopView extends MarkTracker(View) {

View file

@ -48,7 +48,7 @@ export class InPlaceSketcher {
this.ctx.streams.ui.toolbars.headsUpShowTitles.next(false);
let sketchData = this.ctx.services.storage.get(this.sketchStorageKey);
this.viewer.historyManager.init(sketchData);
this.viewer.historyManager.init(sketchData, md);
this.viewer.io.loadSketch(sketchData);
this.ctx.streams.sketcher.sketchingFace.next(face);
this.ctx.streams.sketcher.sketcherAppContext.next(this.sketcherAppContext);

View file

@ -106,7 +106,7 @@ export class GCCircle extends ContractibleObject {
static TYPE = 'GCCircle';
static newInstance(x, y, r) {
return GCCircle().init(new GCPoint(x, y), new GCParam(r))
return GCCircle().init(new GCPoint(x, y), md)
}
init(c, r) {

View file

@ -47,7 +47,7 @@ export class Project {
let sketchId = this.getSketchId();
let sketchData = localStorage.getItem(sketchId);
if (sketchData != null) {
this.viewer.historyManager.init(sketchData);
this.viewer.historyManager.init(sketchData, md);
this.viewer.io.loadSketch(sketchData);
}
this.viewer.repaint();