mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-06 16:33:15 +01:00
288 lines
No EOL
5.4 KiB
TypeScript
288 lines
No EOL
5.4 KiB
TypeScript
import {Generator} from '../id-generator'
|
|
import {Shape} from './shape'
|
|
import {Styles} from "../styles";
|
|
import {NoIcon} from "../icons/NoIcon";
|
|
import {Layer, Viewer} from "../viewer2d";
|
|
import {NOOP} from "gems/func";
|
|
import {SolvableObject} from "../constr/solvableObject";
|
|
|
|
export abstract class SketchObject extends Shape implements SolvableObject {
|
|
|
|
ref: string;
|
|
id: string;
|
|
parent: SketchObject = null;
|
|
markers: any[] = [];
|
|
children: SketchObject[] =[];
|
|
layer: Layer = null;
|
|
constraints: Set<any> = new Set();
|
|
readOnly: boolean = false;
|
|
fullyConstrained: boolean = false;
|
|
generator: any = null;
|
|
generators: Set<any> = new Set();
|
|
_stage: any = null;
|
|
|
|
protected constructor(id: string) {
|
|
super();
|
|
this.ref= Generator.genID() + '';
|
|
this.id = id || this.ref;
|
|
}
|
|
|
|
dependsOn(obj: SketchObject): boolean {
|
|
return false;
|
|
}
|
|
|
|
get isGenerated() {
|
|
let obj: SketchObject = this;
|
|
while (obj) {
|
|
if (obj.generator) {
|
|
return true;
|
|
}
|
|
obj = obj.parent;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
get stage() {
|
|
if (this._stage) {
|
|
return this._stage;
|
|
}
|
|
if (this.parent) {
|
|
return this.parent.stage;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
set stage(value) {
|
|
this._stage = value;
|
|
}
|
|
|
|
normalDistance(aim, scale) {
|
|
return -1;
|
|
}
|
|
|
|
addChild(child) {
|
|
this.children.push(child);
|
|
child.parent = this;
|
|
}
|
|
|
|
accept(visitor) {
|
|
for (let child of this.children) {
|
|
if (!child.accept(visitor)) {
|
|
return false;
|
|
}
|
|
}
|
|
return visitor(this);
|
|
}
|
|
|
|
traverse(visitor) {
|
|
for (let child of this.children) {
|
|
child.traverse(visitor);
|
|
}
|
|
visitor(this);
|
|
}
|
|
|
|
stabilize(viewer) {
|
|
}
|
|
|
|
syncGeometry() {
|
|
}
|
|
|
|
recoverIfNecessary() {
|
|
return false;
|
|
}
|
|
|
|
visitLinked(cb) {
|
|
cb(this);
|
|
}
|
|
|
|
translate(dx, dy) {
|
|
// this.translateImpl(dx, dy);
|
|
if (this.readOnly) {
|
|
return;
|
|
}
|
|
this.visitLinked(obj => {
|
|
obj.translateImpl(dx, dy);
|
|
obj.ancestry(a => a.syncGeometry());
|
|
});
|
|
}
|
|
|
|
translateImpl(dx, dy) {
|
|
this.accept(function (obj) {
|
|
if (obj.TYPE === 'Point') {
|
|
obj.translate(dx, dy);
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
addMarker(style) {
|
|
this.markers.push(style);
|
|
this.markers.sort((a, b) => (a.priority||99999) - (b.priority||99999))
|
|
}
|
|
|
|
removeMarker(style) {
|
|
const index = this.markers.indexOf(style);
|
|
if (index !== -1) {
|
|
this.markers.splice(index, 1);
|
|
}
|
|
}
|
|
|
|
get marked() {
|
|
return this.markers.length !== 0;
|
|
}
|
|
|
|
abstract drawImpl(ctx: CanvasRenderingContext2D, scale: number, viewer: Viewer);
|
|
|
|
draw(ctx: CanvasRenderingContext2D, scale: number, viewer: Viewer) {
|
|
if (!this.visible) return;
|
|
const customStyle = this.getCustomStyle();
|
|
if (customStyle !== null) {
|
|
ctx.save();
|
|
viewer.setStyle(customStyle, ctx);
|
|
}
|
|
|
|
this.drawImpl(ctx, scale, viewer);
|
|
if (customStyle !== null) ctx.restore();
|
|
}
|
|
|
|
getCustomStyle() {
|
|
const productionKind = this.classify();
|
|
if (this.markers.length !== 0) {
|
|
return this.markers[0];
|
|
} else if (this.isGenerated) {
|
|
return Styles.GENERATED;
|
|
} else if (productionKind === PAST) {
|
|
return Styles.PAST;
|
|
} else if (productionKind === FUTURE) {
|
|
return Styles.FUTURE;
|
|
} else if (this.fullyConstrained) {
|
|
return Styles.FULLY_CONSTRAINED;
|
|
} else {
|
|
}
|
|
return null;
|
|
}
|
|
|
|
classify() {
|
|
if (!this.stage) {
|
|
return CURRENT;
|
|
}
|
|
const thisIndex = this.stage.index;
|
|
const activeIndex = this.stage.viewer.parametricManager.stage.index;
|
|
if (thisIndex < activeIndex) {
|
|
return PAST;
|
|
} else if (thisIndex > activeIndex) {
|
|
return FUTURE;
|
|
} else {
|
|
return CURRENT;
|
|
}
|
|
}
|
|
|
|
copy() {
|
|
throw 'method not implemented';
|
|
}
|
|
|
|
mirror(dest, mirroringFunc) {
|
|
|
|
let sourcePoints = [];
|
|
|
|
pointIterator(this, o => {
|
|
sourcePoints.push(o);
|
|
});
|
|
|
|
let i = 0;
|
|
pointIterator(dest, o => {
|
|
sourcePoints[i++].mirror(o, mirroringFunc);
|
|
});
|
|
}
|
|
|
|
visitParams(callback) {
|
|
throw 'method not implemented';
|
|
}
|
|
|
|
collectParams(params) {
|
|
this.visitParams(p => params.push(p));
|
|
}
|
|
|
|
get simpleClassName() {
|
|
return this.TYPE;
|
|
}
|
|
|
|
get effectiveLayer() {
|
|
let shape: SketchObject = this;
|
|
while (shape) {
|
|
if (shape.layer) {
|
|
return shape.layer;
|
|
}
|
|
shape = shape.parent;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
getConstraintByType(typeId) {
|
|
for (let c of this.constraints) {
|
|
if (c.schema.id === typeId) {
|
|
return c;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
ancestry(cb) {
|
|
let obj: SketchObject = this;
|
|
while (obj) {
|
|
cb(obj);
|
|
obj = obj.parent;
|
|
}
|
|
}
|
|
|
|
root() {
|
|
let obj: SketchObject = this;
|
|
while (obj.parent) {
|
|
obj = obj.parent;
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
get isRoot() {
|
|
return this.parent === null;
|
|
}
|
|
|
|
get icon() {
|
|
return NoIcon;
|
|
}
|
|
|
|
freeze() {
|
|
this.visitParams(param => {
|
|
param.set = NOOP;
|
|
});
|
|
}
|
|
|
|
get labelCenter() {
|
|
let point;
|
|
pointIterator(this, o => {
|
|
if (!point) {
|
|
point = o;
|
|
}
|
|
});
|
|
return point && point.toVector();
|
|
}
|
|
|
|
abstract write(): SketchObjectSerializationData;
|
|
}
|
|
|
|
export interface SketchObjectSerializationData {
|
|
|
|
}
|
|
|
|
export function pointIterator(shape, func) {
|
|
shape.accept(o => {
|
|
if (o.TYPE === 'Point') {
|
|
func(o);
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
const PAST = 1;
|
|
const CURRENT = 2;
|
|
const FUTURE = 3; |