diff --git a/packages/ketcher-react/src/icons/files/shape-circle.svg b/packages/ketcher-react/src/icons/files/shape-circle.svg
deleted file mode 100644
index 0019f4a994..0000000000
--- a/packages/ketcher-react/src/icons/files/shape-circle.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/packages/ketcher-react/src/icons/files/shape-ellipse.svg b/packages/ketcher-react/src/icons/files/shape-ellipse.svg
new file mode 100644
index 0000000000..2c848facb6
--- /dev/null
+++ b/packages/ketcher-react/src/icons/files/shape-ellipse.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/ketcher-react/src/icons/index.tsx b/packages/ketcher-react/src/icons/index.tsx
index a701faf02e..e913b9ed12 100644
--- a/packages/ketcher-react/src/icons/index.tsx
+++ b/packages/ketcher-react/src/icons/index.tsx
@@ -82,7 +82,7 @@ import TransformRotateIcon from './files/transform-rotate.svg'
import UndoIcon from './files/undo.svg'
import ZoomInIcon from './files/zoom-in.svg'
import ZoomOutIcon from './files/zoom-out.svg'
-import ShapeCircleIcon from './files/shape-circle.svg'
+import ShapeEllipseIcon from './files/shape-ellipse.svg'
import ShapeRectangleIcon from './files/shape-rectangle.svg'
import ShapePolylineIcon from './files/shape-polyline.svg'
import ShapeLineIcon from './files/shape-line.svg'
@@ -157,7 +157,7 @@ const icons = {
undo: UndoIcon,
'zoom-in': ZoomInIcon,
'zoom-out': ZoomOutIcon,
- 'shape-circle': ShapeCircleIcon,
+ 'shape-ellipse': ShapeEllipseIcon,
'shape-rectangle': ShapeRectangleIcon,
'shape-polyline': ShapePolylineIcon,
'shape-line': ShapeLineIcon
diff --git a/packages/ketcher-react/src/script/chem/struct/index.js b/packages/ketcher-react/src/script/chem/struct/index.js
index 43abc2d1ac..4508fec467 100644
--- a/packages/ketcher-react/src/script/chem/struct/index.js
+++ b/packages/ketcher-react/src/script/chem/struct/index.js
@@ -28,6 +28,7 @@ import Fragment from './fragment'
import SGroup from './sgroup'
import RGroup from './rgroup'
import SGroupForest from './sgforest'
+import { SimpleObject, SimpleObjectMode } from './simpleObject'
function Struct() {
this.atoms = new Pool()
@@ -1042,31 +1043,6 @@ RxnPlus.prototype.clone = function () {
return new RxnPlus(this)
}
-function SimpleObject(params) {
- params = params || {}
- this.pos = []
-
- if (params.pos)
- for (let i = 0; i < params.pos.length; i++)
- this.pos[i] = params.pos[i] ? new Vec2(params.pos[i]) : new Vec2()
-
- this.mode = params.mode
-}
-
-SimpleObject.prototype.clone = function () {
- return new SimpleObject(this)
-}
-
-SimpleObject.prototype.center = function () {
- switch (this.mode) {
- case 'rectangle': {
- return Vec2.centre(this.pos[0], this.pos[1])
- }
- default:
- return this.pos[0]
- }
-}
-
function RxnArrow(params) {
params = params || {}
this.pp = params.pp ? new Vec2(params.pp) : new Vec2()
@@ -1095,5 +1071,6 @@ export {
RGroup,
RxnPlus,
RxnArrow,
- SimpleObject
+ SimpleObject,
+ SimpleObjectMode
}
diff --git a/packages/ketcher-react/src/script/chem/struct/simpleObject.ts b/packages/ketcher-react/src/script/chem/struct/simpleObject.ts
new file mode 100644
index 0000000000..745e016a58
--- /dev/null
+++ b/packages/ketcher-react/src/script/chem/struct/simpleObject.ts
@@ -0,0 +1,53 @@
+/****************************************************************************
+ * Copyright 2021 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ***************************************************************************/
+import Vec2 from '../../util/vec2'
+
+class SimpleObject {
+ pos: Array
+ mode: SimpleObjectMode
+
+ constructor(params: { mode: SimpleObjectMode; pos?: Array }) {
+ params = params || {}
+ this.pos = []
+
+ if (params.pos)
+ for (let i = 0; i < params.pos.length; i++)
+ this.pos[i] = params.pos[i] ? new Vec2(params.pos[i]) : new Vec2()
+
+ this.mode = params.mode
+ }
+
+ clone(): SimpleObject {
+ return new SimpleObject(this)
+ }
+
+ center(): Vec2 {
+ switch (this.mode) {
+ case SimpleObjectMode.rectangle: {
+ return Vec2.centre(this.pos[0], this.pos[1])
+ }
+ default:
+ return this.pos[0]
+ }
+ }
+}
+
+enum SimpleObjectMode {
+ ellipse = 'ellipse',
+ rectangle = 'rectangle',
+ line = 'line'
+}
+export { SimpleObject, SimpleObjectMode }
diff --git a/packages/ketcher-react/src/script/editor/actions/simpleobject.js b/packages/ketcher-react/src/script/editor/actions/simpleobject.ts
similarity index 77%
rename from packages/ketcher-react/src/script/editor/actions/simpleobject.js
rename to packages/ketcher-react/src/script/editor/actions/simpleobject.ts
index 82f6bc71d1..4c13a359e6 100644
--- a/packages/ketcher-react/src/script/editor/actions/simpleobject.js
+++ b/packages/ketcher-react/src/script/editor/actions/simpleobject.ts
@@ -22,14 +22,23 @@ export function fromSimpleObjectDeletion(restruct, id) {
return action.perform(restruct)
}
-export function fromSimpleObjectAddition(restruct, pos, mode) {
+export function fromSimpleObjectAddition(restruct, pos, mode, toCircle) {
var action = new Action()
- action.addOp(new op.SimpleObjectAdd(pos, mode))
+ action.addOp(new op.SimpleObjectAdd(pos, mode, toCircle))
return action.perform(restruct)
}
-export function fromSimpleObjectResizing(restruct, id, d, current, anchor) {
+export function fromSimpleObjectResizing(
+ restruct,
+ id,
+ d,
+ current,
+ anchor,
+ toCircle
+) {
var action = new Action()
- action.addOp(new op.SimpleObjectResize(id, d, current, anchor))
+ action.addOp(
+ new op.SimpleObjectResize(id, d, current, anchor, false, toCircle)
+ )
return action.perform(restruct)
}
diff --git a/packages/ketcher-react/src/script/editor/operations/base.js b/packages/ketcher-react/src/script/editor/operations/base.js
index 1852de2fad..53fd9b5860 100644
--- a/packages/ketcher-react/src/script/editor/operations/base.js
+++ b/packages/ketcher-react/src/script/editor/operations/base.js
@@ -5,7 +5,7 @@ class Base {
this.type = type
}
- execute() {
+ execute(restruct) {
throw new Error('Operation.execute() is not implemented')
}
@@ -27,6 +27,49 @@ class Base {
}
}
+export const OperationType = {
+ ATOM_ADD: 'Add atom',
+ ATOM_DELETE: 'Delete atom',
+ ATOM_ATTR: 'Set atom attribute',
+ ATOM_MOVE: 'Move atom',
+ BOND_ADD: 'Add bond',
+ BOND_DELETE: 'Delete bond',
+ BOND_ATTR: 'Set bond attribute',
+ BOND_MOVE: 'Move bond',
+ LOOP_MOVE: 'Move loop',
+ S_GROUP_ATOM_ADD: 'Add atom to s-group',
+ S_GROUP_ATOM_REMOVE: 'Remove atom from s-group',
+ S_GROUP_ATTR: 'Set s-group attribute',
+ S_GROUP_CREATE: 'Create s-group',
+ S_GROUP_DELETE: 'Delete s-group',
+ S_GROUP_ADD_TO_HIERACHY: 'Add s-group to hierarchy',
+ S_GROUP_REMOVE_FROM_HIERACHY: 'Delete s-group from hierarchy',
+ R_GROUP_ATTR: 'Set r-group attribute',
+ R_GROUP_FRAGMENT: 'R-group fragment',
+ UPDATE_IF_THEN: 'Update',
+ RESTORE_IF_THEN: 'Restore',
+ RXN_ARROW_ADD: 'Add rxn arrow',
+ RXN_ARROW_DELETE: 'Delete rxn arrow',
+ RXN_ARROW_MOVE: 'Move rxn arrow',
+ RXN_PLUS_ADD: 'Add rxn plus',
+ RXN_PLUS_DELETE: 'Delete rxn plus',
+ RXN_PLUS_MOVE: 'Move rxn plus',
+ S_GROUP_DATA_MOVE: 'Move s-group data',
+ CANVAS_LOAD: 'Load canvas',
+ ALIGN_DESCRIPTORS: 'Align descriptors',
+ SIMPLE_OBJECT_ADD: 'Add simple object',
+ SIMPLE_OBJECT_DELETE: 'Delete simple object',
+ SIMPLE_OBJECT_MOVE: 'Move simple object',
+ SIMPLE_OBJECT_RESIZE: 'Resize simple object',
+ RESTORE_DESCRIPTORS_POSITION: 'Restore descriptors position',
+ FRAGMENT_ADD: 'Add fragment',
+ FRAGMENT_DELETE: 'Delete fragment',
+ FRAGMENT_STEREO_FLAG: 'Add fragment stereo flag',
+ FRAGMENT_ADD_STEREO_ATOM: 'Add stereo atom to fragment',
+ FRAGMENT_DELETE_STEREO_ATOM: 'Delete stereo atom from fragment',
+ ENHANCED_FLAG_MOVE: 'Move enhanced flag'
+}
+
export function invalidateAtom(restruct, aid, level) {
const atom = restruct.atoms.get(aid)
diff --git a/packages/ketcher-react/src/script/editor/operations/op.js b/packages/ketcher-react/src/script/editor/operations/op.js
index 1f24da1a17..20b4f64701 100644
--- a/packages/ketcher-react/src/script/editor/operations/op.js
+++ b/packages/ketcher-react/src/script/editor/operations/op.js
@@ -23,8 +23,7 @@ import {
RGroup,
RxnArrow,
RxnPlus,
- SGroup,
- SimpleObject
+ SGroup
} from '../../chem/struct'
import {
ReAtom,
@@ -32,15 +31,15 @@ import {
ReRxnPlus,
ReRxnArrow,
ReRGroup,
- ReSGroup,
- ReSimpleObject
+ ReSGroup
} from '../../render/restruct'
import Base, {
invalidateAtom,
invalidateBond,
invalidateItem,
- invalidateLoop
+ invalidateLoop,
+ OperationType
} from './base'
import {
FragmentAdd,
@@ -51,50 +50,14 @@ import {
EnhancedFlagMove
} from './op-frag'
-const tfx = util.tfx
+import {
+ SimpleObjectAdd,
+ SimpleObjectDelete,
+ SimpleObjectMove,
+ SimpleObjectResize
+} from './simpleObject'
-export const OperationType = {
- ATOM_ADD: 'Add atom',
- ATOM_DELETE: 'Delete atom',
- ATOM_ATTR: 'Set atom attribute',
- ATOM_MOVE: 'Move atom',
- BOND_ADD: 'Add bond',
- BOND_DELETE: 'Delete bond',
- BOND_ATTR: 'Set bond attribute',
- BOND_MOVE: 'Move bond',
- LOOP_MOVE: 'Move loop',
- S_GROUP_ATOM_ADD: 'Add atom to s-group',
- S_GROUP_ATOM_REMOVE: 'Remove atom from s-group',
- S_GROUP_ATTR: 'Set s-group attribute',
- S_GROUP_CREATE: 'Create s-group',
- S_GROUP_DELETE: 'Delete s-group',
- S_GROUP_ADD_TO_HIERACHY: 'Add s-group to hierarchy',
- S_GROUP_REMOVE_FROM_HIERACHY: 'Delete s-group from hierarchy',
- R_GROUP_ATTR: 'Set r-group attribute',
- R_GROUP_FRAGMENT: 'R-group fragment',
- UPDATE_IF_THEN: 'Update',
- RESTORE_IF_THEN: 'Restore',
- RXN_ARROW_ADD: 'Add rxn arrow',
- RXN_ARROW_DELETE: 'Delete rxn arrow',
- RXN_ARROW_MOVE: 'Move rxn arrow',
- RXN_PLUS_ADD: 'Add rxn plus',
- RXN_PLUS_DELETE: 'Delete rxn plus',
- RXN_PLUS_MOVE: 'Move rxn plus',
- S_GROUP_DATA_MOVE: 'Move s-group data',
- CANVAS_LOAD: 'Load canvas',
- ALIGN_DESCRIPTORS: 'Align descriptors',
- SIMPLE_OBJECT_ADD: 'Add simple object',
- SIMPLE_OBJECT_DELETE: 'Delete simple object',
- SIMPLE_OBJECT_MOVE: 'Move simple object',
- SIMPLE_OBJECT_RESIZE: 'Resize simple object',
- RESTORE_DESCRIPTORS_POSITION: 'Restore descriptors position',
- FRAGMENT_ADD: 'Add fragment',
- FRAGMENT_DELETE: 'Delete fragment',
- FRAGMENT_STEREO_FLAG: 'Add fragment stereo flag',
- FRAGMENT_ADD_STEREO_ATOM: 'Add stereo atom to fragment',
- FRAGMENT_DELETE_STEREO_ATOM: 'Delete stereo atom from fragment',
- ENHANCED_FLAG_MOVE: 'Move enhanced flag'
-}
+const tfx = util.tfx
function AtomAdd(atom, pos) {
this.data = { atom, pos, aid: null }
@@ -997,159 +960,6 @@ RestoreDescriptorsPosition.prototype.invert = function () {
return new AlignDescriptors()
}
-function SimpleObjectAdd(pos, mode) {
- this.data = { id: null, pos, mode }
- this.performed = false
-}
-
-SimpleObjectAdd.prototype = new Base(OperationType.SIMPLE_OBJECT_ADD)
-
-SimpleObjectAdd.prototype.execute = function (restruct) {
- const struct = restruct.molecule
- if (!this.performed) {
- this.data.id = struct.simpleObjects.add(
- new SimpleObject({ mode: this.data.mode })
- )
- this.performed = true
- } else {
- struct.simpleObjects.set(
- this.data.id,
- new SimpleObject({ mode: this.data.mode })
- )
- }
-
- restruct.simpleObjects.set(
- this.data.id,
- new ReSimpleObject(struct.simpleObjects.get(this.data.id))
- )
-
- struct.simpleObjectSetPos(
- this.data.id,
- this.data.pos.map(p => new Vec2(p))
- )
-
- invalidateItem(restruct, 'simpleObjects', this.data.id, 1)
-}
-
-SimpleObjectAdd.prototype.invert = function () {
- const ret = new SimpleObjectDelete()
- ret.data = this.data
- return ret
-}
-
-function SimpleObjectDelete(id) {
- this.data = { id, pos: null, item: null }
- this.performed = false
-}
-
-SimpleObjectDelete.prototype = new Base(OperationType.SIMPLE_OBJECT_DELETE)
-
-SimpleObjectDelete.prototype.execute = function (restruct) {
- const struct = restruct.molecule
- if (!this.performed) {
- const item = struct.simpleObjects.get(this.data.id)
- this.data.pos = item.pos
- this.data.mode = item.mode
- this.performed = true
- }
-
- restruct.markItemRemoved()
- restruct.clearVisel(restruct.simpleObjects.get(this.data.id).visel)
- restruct.simpleObjects.delete(this.data.id)
-
- struct.simpleObjects.delete(this.data.id)
-}
-
-SimpleObjectDelete.prototype.invert = function () {
- const ret = new SimpleObjectAdd()
- ret.data = this.data
- return ret
-}
-
-function SimpleObjectMove(id, d, noinvalidate) {
- this.data = { id, d, noinvalidate }
-}
-
-SimpleObjectMove.prototype = new Base(OperationType.SIMPLE_OBJECT_MOVE)
-
-SimpleObjectMove.prototype.execute = function (restruct) {
- const struct = restruct.molecule
- const id = this.data.id
- const d = this.data.d
- const item = struct.simpleObjects.get(id)
- item.pos.forEach(p => p.add_(d))
- restruct.simpleObjects
- .get(id)
- .visel.translate(scale.obj2scaled(d, restruct.render.options))
- this.data.d = d.negated()
- if (!this.data.noinvalidate) invalidateItem(restruct, 'simpleObjects', id, 1)
-}
-
-SimpleObjectMove.prototype.invert = function () {
- const ret = new SimpleObjectMove()
- ret.data = this.data
- return ret
-}
-
-function SimpleObjectResize(id, d, current, anchor, noinvalidate) {
- this.data = { id, d, current, anchor, noinvalidate }
-}
-
-SimpleObjectResize.prototype = new Base(OperationType.SIMPLE_OBJECT_RESIZE)
-
-SimpleObjectResize.prototype.execute = function (restruct) {
- const struct = restruct.molecule
- const id = this.data.id
- const d = this.data.d
- const current = this.data.current
- const item = struct.simpleObjects.get(id)
- const anchor = this.data.anchor
-
- if (item.mode === 'circle') {
- const previousPos1 = item.pos[1].get_xy0()
- item.pos[1].x = current.x
- item.pos[1].y = current.y
- this.data.current = previousPos1
- } else if (item.mode === 'line' && anchor) {
- const previousPos1 = anchor.get_xy0()
- anchor.x = current.x
- anchor.y = current.y
- this.data.current = previousPos1
- } else if (item.mode === 'rectangle' && anchor) {
- const previousPos0 = item.pos[0].get_xy0()
- const previousPos1 = item.pos[1].get_xy0()
-
- if (tfx(anchor.x) === tfx(item.pos[1].x)) {
- item.pos[1].x = anchor.x = current.x
- this.data.current.x = previousPos1.x
- }
- if (tfx(anchor.y) === tfx(item.pos[1].y)) {
- item.pos[1].y = anchor.y = current.y
- this.data.current.y = previousPos1.y
- }
- if (tfx(anchor.x) === tfx(item.pos[0].x)) {
- item.pos[0].x = anchor.x = current.x
- this.data.current.x = previousPos0.x
- }
- if (tfx(anchor.y) === tfx(item.pos[0].y)) {
- item.pos[0].y = anchor.y = current.y
- this.data.current.y = previousPos0.y
- }
- } else item.pos[1].add_(d)
-
- restruct.simpleObjects
- .get(id)
- .visel.translate(scale.obj2scaled(d, restruct.render.options))
- this.data.d = d.negated()
- if (!this.data.noinvalidate) invalidateItem(restruct, 'simpleObjects', id, 1)
-}
-
-SimpleObjectResize.prototype.invert = function () {
- const ret = new SimpleObjectResize()
- ret.data = this.data
- return ret
-}
-
const operations = {
AtomAdd,
AtomDelete,
@@ -1194,3 +1004,5 @@ const operations = {
}
export default operations
+
+export { OperationType }
diff --git a/packages/ketcher-react/src/script/editor/operations/simpleObject.ts b/packages/ketcher-react/src/script/editor/operations/simpleObject.ts
new file mode 100644
index 0000000000..525c8cff1a
--- /dev/null
+++ b/packages/ketcher-react/src/script/editor/operations/simpleObject.ts
@@ -0,0 +1,334 @@
+/****************************************************************************
+ * Copyright 2021 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ***************************************************************************/
+import Vec2 from '../../util/vec2'
+import Base, { invalidateItem, OperationType } from './base'
+import { ReSimpleObject } from '../../render/restruct'
+import { SimpleObject, SimpleObjectMode } from 'src/script/chem/struct'
+import scale from '../../util/scale'
+import util from '../../render/util'
+
+const tfx = util.tfx
+
+interface SimpleObjectAddData {
+ id?: string
+ pos: Array
+ mode: SimpleObjectMode
+ toCircle: boolean
+}
+export class SimpleObjectAdd extends Base {
+ data: SimpleObjectAddData
+ performed: boolean
+
+ constructor(
+ pos: Array = [],
+ mode: SimpleObjectMode = SimpleObjectMode.line,
+ toCircle: boolean = false
+ ) {
+ super(OperationType.SIMPLE_OBJECT_ADD)
+ // here is "tempValue is used
+ this.data = { pos, mode, toCircle }
+ this.performed = false
+ }
+
+ execute(restruct: any): void {
+ const struct = restruct.molecule
+ if (!this.performed) {
+ this.data.id = struct.simpleObjects.add(
+ new SimpleObject({ mode: this.data.mode })
+ )
+ this.performed = true
+ } else {
+ struct.simpleObjects.set(
+ this.data.id,
+ new SimpleObject({ mode: this.data.mode })
+ )
+ }
+
+ restruct.simpleObjects.set(
+ this.data.id,
+ new ReSimpleObject(struct.simpleObjects.get(this.data.id))
+ )
+
+ const positions = [...this.data.pos]
+ if (this.data.toCircle) {
+ positions[1] = makeCircleFromEllipse(positions[0], positions[1])
+ }
+ struct.simpleObjectSetPos(
+ this.data.id,
+ positions.map(p => new Vec2(p))
+ )
+
+ invalidateItem(restruct, 'simpleObjects', this.data.id, 1)
+ }
+ invert(): Base {
+ //@ts-ignore
+ return new SimpleObjectDelete(this.data.id)
+ }
+}
+
+interface SimpleObjectDeleteData {
+ id: string
+ pos?: Array
+ mode?: SimpleObjectMode
+ toCircle?: boolean
+}
+
+export class SimpleObjectDelete extends Base {
+ data: SimpleObjectDeleteData
+ performed: boolean
+
+ constructor(id: string) {
+ super(OperationType.SIMPLE_OBJECT_DELETE)
+ this.data = { id, pos: [], mode: SimpleObjectMode.line, toCircle: false }
+ this.performed = false
+ }
+
+ execute(restruct: any): void {
+ const struct = restruct.molecule
+ if (!this.performed) {
+ const item = struct.simpleObjects.get(this.data.id) as any
+ //save to data current values. In future they could be used in invert for restoring simple object
+ this.data.pos = item.pos
+ this.data.mode = item.mode
+ this.data.toCircle = item.toCircle
+ this.performed = true
+ }
+
+ restruct.markItemRemoved()
+ restruct.clearVisel(restruct.simpleObjects.get(this.data.id).visel)
+ restruct.simpleObjects.delete(this.data.id)
+
+ struct.simpleObjects.delete(this.data.id)
+ }
+
+ invert(): Base {
+ return new SimpleObjectAdd(
+ this.data.pos,
+ this.data.mode,
+ this.data.toCircle
+ )
+ }
+}
+
+interface SimpleObjectMoveData {
+ id: string
+ d: any
+ noinvalidate: boolean
+}
+
+export class SimpleObjectMove extends Base {
+ data: SimpleObjectMoveData
+
+ constructor(id: string, d: any, noinvalidate: boolean) {
+ super(OperationType.SIMPLE_OBJECT_MOVE)
+ this.data = { id, d, noinvalidate }
+ }
+ execute(restruct: any): void {
+ const struct = restruct.molecule
+ const id = this.data.id
+ const d = this.data.d
+ const item = struct.simpleObjects.get(id)
+ item.pos.forEach(p => p.add_(d))
+ restruct.simpleObjects
+ .get(id)
+ .visel.translate(scale.obj2scaled(d, restruct.render.options))
+ this.data.d = d.negated()
+ if (!this.data.noinvalidate)
+ invalidateItem(restruct, 'simpleObjects', id, 1)
+ }
+
+ invert(): Base {
+ const move = new SimpleObjectMove(
+ this.data.id,
+ this.data.d,
+ this.data.noinvalidate
+ )
+ //todo Need further investigation on why this is needed?
+ move.data = this.data
+ return move
+ }
+}
+
+interface SimpleObjectResizeData {
+ id: string
+ d: any
+ current: Vec2
+ anchor: Vec2
+ noinvalidate: boolean
+ toCircle: boolean
+}
+
+function handleEllipseChangeIfAnchorIsOnAxis(anchor, item, current) {
+ const previousPos0 = item.pos[0].get_xy0()
+ const previousPos1 = item.pos[1].get_xy0()
+ if (tfx(anchor.x) === tfx(item.pos[1].x)) {
+ item.pos[1].x = anchor.x = current.x
+ current.x = previousPos1.x
+ }
+ if (tfx(anchor.y) === tfx(item.pos[1].y)) {
+ item.pos[1].y = anchor.y = current.y
+ current.y = previousPos1.y
+ }
+ if (tfx(anchor.x) === tfx(item.pos[0].x)) {
+ item.pos[0].x = anchor.x = current.x
+ current.x = previousPos0.x
+ }
+ if (tfx(anchor.y) === tfx(item.pos[0].y)) {
+ item.pos[0].y = anchor.y = current.y
+ current.y = previousPos0.y
+ }
+}
+
+function handleEllipseChangeIfAnchorIsOnDiagonal(item, current) {
+ const rad = Vec2.diff(item.pos[1], item.pos[0])
+ const rx = Math.abs(rad.x / 2)
+ const ry = Math.abs(rad.y / 2)
+ const topLeftX = item.pos[0].x <= item.pos[1].x ? item.pos[0] : item.pos[1]
+ const topLeftY = item.pos[0].y <= item.pos[1].y ? item.pos[0] : item.pos[1]
+ const bottomRightX =
+ item.pos[0].x <= item.pos[1].x ? item.pos[1] : item.pos[0]
+ const bottomRightY =
+ item.pos[0].y <= item.pos[1].y ? item.pos[1] : item.pos[0]
+ //check in which quarter the anchor is placed
+ const firstQuarter =
+ current.x > topLeftX.x + rx && current.y <= topLeftY.y + ry
+ const secondQuarter =
+ current.x <= topLeftX.x + rx && current.y <= topLeftY.y + ry
+ const thirdQuarter =
+ current.x <= topLeftX.x + rx && current.y > topLeftY.y + ry
+ const forthQuarter =
+ current.x > topLeftX.x + rx && current.y > topLeftY.y + ry
+
+ if (current.x > topLeftX.x && (firstQuarter || forthQuarter)) {
+ bottomRightX.x = current.x
+ }
+
+ if (current.y < bottomRightY.y && (firstQuarter || secondQuarter)) {
+ topLeftY.y = current.y
+ }
+ if (current.x < bottomRightX.x && (secondQuarter || thirdQuarter)) {
+ topLeftX.x = current.x
+ }
+ if (current.y > topLeftY.y && (thirdQuarter || forthQuarter)) {
+ bottomRightY.y = current.y
+ }
+}
+
+function handleRectangleChangeWithAnchor(item, anchor, current) {
+ const previousPos0 = item.pos[0].get_xy0()
+ const previousPos1 = item.pos[1].get_xy0()
+
+ if (tfx(anchor.x) === tfx(item.pos[1].x)) {
+ item.pos[1].x = anchor.x = current.x
+ current.x = previousPos1.x
+ }
+ if (tfx(anchor.y) === tfx(item.pos[1].y)) {
+ item.pos[1].y = anchor.y = current.y
+ current.y = previousPos1.y
+ }
+ if (tfx(anchor.x) === tfx(item.pos[0].x)) {
+ item.pos[0].x = anchor.x = current.x
+ current.x = previousPos0.x
+ }
+ if (tfx(anchor.y) === tfx(item.pos[0].y)) {
+ item.pos[0].y = anchor.y = current.y
+ current.y = previousPos0.y
+ }
+}
+
+export class SimpleObjectResize extends Base {
+ data: SimpleObjectResizeData
+
+ constructor(
+ id: string,
+ d: any,
+ current: Vec2,
+ anchor: any,
+ noinvalidate: boolean,
+ toCircle: boolean
+ ) {
+ super(OperationType.SIMPLE_OBJECT_RESIZE)
+ this.data = { id, d, current, anchor, noinvalidate, toCircle }
+ }
+
+ execute(restruct: any): void {
+ const struct = restruct.molecule
+ const id = this.data.id
+ const d = this.data.d
+ const current = this.data.current
+ const item = struct.simpleObjects.get(id)
+ const anchor = this.data.anchor
+ if (item.mode === SimpleObjectMode.ellipse) {
+ if (anchor) {
+ if (
+ tfx(anchor.y) !== tfx(item.pos[0].y) &&
+ tfx(anchor.x) !== tfx(item.pos[0].x) &&
+ tfx(anchor.y) !== tfx(item.pos[1].y) &&
+ tfx(anchor.x) !== tfx(item.pos[1].x)
+ ) {
+ handleEllipseChangeIfAnchorIsOnDiagonal(item, current)
+ } else {
+ handleEllipseChangeIfAnchorIsOnAxis(anchor, item, current)
+ }
+ } else if (this.data.toCircle) {
+ const previousPos1 = item.pos[1].get_xy0()
+ const circlePoint = makeCircleFromEllipse(item.pos[0], current)
+ item.pos[1].x = circlePoint.x
+ item.pos[1].y = circlePoint.y
+ this.data.current = previousPos1
+ } else {
+ const previousPos1 = item.pos[1].get_xy0()
+ item.pos[1].x = current.x
+ item.pos[1].y = current.y
+ this.data.current = previousPos1
+ }
+ } else if (item.mode === SimpleObjectMode.line && anchor) {
+ const previousPos1 = anchor.get_xy0()
+ anchor.x = current.x
+ anchor.y = current.y
+ this.data.current = previousPos1
+ } else if (item.mode === SimpleObjectMode.rectangle && anchor) {
+ handleRectangleChangeWithAnchor(item, anchor, current)
+ } else item.pos[1].add_(d)
+
+ restruct.simpleObjects
+ .get(id)
+ .visel.translate(scale.obj2scaled(d, restruct.render.options))
+ this.data.d = d.negated()
+ if (!this.data.noinvalidate)
+ invalidateItem(restruct, 'simpleObjects', id, 1)
+ }
+ invert(): Base {
+ return new SimpleObjectResize(
+ this.data.id,
+ this.data.d,
+ this.data.current,
+ this.data.anchor,
+ this.data.noinvalidate,
+ this.data.toCircle
+ )
+ }
+}
+
+export function makeCircleFromEllipse(position0: Vec2, position1: Vec2): Vec2 {
+ const diff = Vec2.diff(position1, position0)
+ const min = Math.abs(diff.x) < Math.abs(diff.y) ? diff.x : diff.y
+ return new Vec2(
+ position0.x + (diff.x > 0 ? 1 : -1) * Math.abs(min),
+ position0.y + (diff.y > 0 ? 1 : -1) * Math.abs(min),
+ 0
+ )
+}
diff --git a/packages/ketcher-react/src/script/editor/tool/simpleobject.js b/packages/ketcher-react/src/script/editor/tool/simpleobject.js
index 65f01789cc..87bae4388b 100644
--- a/packages/ketcher-react/src/script/editor/tool/simpleobject.js
+++ b/packages/ketcher-react/src/script/editor/tool/simpleobject.js
@@ -64,7 +64,8 @@ SimpleObjectTool.prototype.mousemove = function (event) {
this.dragCtx.ci.id,
diff,
current,
- this.dragCtx.ci.ref
+ this.dragCtx.ci.ref,
+ event.shiftKey
)
}
this.editor.update(this.dragCtx.action, true)
@@ -90,7 +91,8 @@ SimpleObjectTool.prototype.mousemove = function (event) {
this.dragCtx.itemId,
diff,
current,
- null
+ null,
+ event.shiftKey
)
this.editor.update(this.dragCtx.action, true)
}
@@ -113,7 +115,8 @@ SimpleObjectTool.prototype.mouseup = function (event) {
this.dragCtx.action = fromSimpleObjectAddition(
rnd.ctab,
[this.dragCtx.p0, this.dragCtx.previous],
- this.mode
+ this.mode,
+ event.shiftKey
)
}
this.editor.update(this.dragCtx.action)
diff --git a/packages/ketcher-react/src/script/format/chemGraph/fromGraph/simpleObjectToStruct.js b/packages/ketcher-react/src/script/format/chemGraph/fromGraph/simpleObjectToStruct.js
deleted file mode 100644
index aeec67da21..0000000000
--- a/packages/ketcher-react/src/script/format/chemGraph/fromGraph/simpleObjectToStruct.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/****************************************************************************
- * Copyright 2020 EPAM Systems
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- ***************************************************************************/
-import { SimpleObject } from '../../../chem/struct'
-export function simpleObjectToStruct(graphItem, struct) {
- struct.simpleObjects.add(new SimpleObject(graphItem.data))
- return struct
-}
diff --git a/packages/ketcher-react/src/script/format/chemGraph/fromGraph/simpleObjectToStruct.ts b/packages/ketcher-react/src/script/format/chemGraph/fromGraph/simpleObjectToStruct.ts
new file mode 100644
index 0000000000..2547670293
--- /dev/null
+++ b/packages/ketcher-react/src/script/format/chemGraph/fromGraph/simpleObjectToStruct.ts
@@ -0,0 +1,51 @@
+/****************************************************************************
+ * Copyright 2021 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ***************************************************************************/
+import { SimpleObject, SimpleObjectMode } from '../../../chem/struct'
+import Vec2 from '../../../util/vec2'
+
+export function simpleObjectToStruct(graphItem, struct) {
+ const object =
+ graphItem.data.mode === 'circle'
+ ? circleToEllipse(graphItem)
+ : graphItem.data
+ struct.simpleObjects.add(new SimpleObject(object))
+ return struct
+}
+
+/**
+ * @deprecated TODO to remove after release 2.3
+ * As circle has been migrated to ellipses here is function for converting old files data with circles to ellipse type
+ * @param graphItem
+ */
+function circleToEllipse(graphItem) {
+ const radius = Vec2.dist(graphItem.data.pos[1], graphItem.data.pos[0])
+ const pos0 = graphItem.data.pos[0]
+ return {
+ mode: SimpleObjectMode.ellipse,
+ pos: [
+ {
+ x: pos0.x - Math.abs(radius),
+ y: pos0.y - Math.abs(radius),
+ z: pos0.z - Math.abs(radius)
+ },
+ {
+ x: pos0.x + Math.abs(radius),
+ y: pos0.y + Math.abs(radius),
+ z: pos0.z + Math.abs(radius)
+ }
+ ]
+ }
+}
diff --git a/packages/ketcher-react/src/script/render/draw.js b/packages/ketcher-react/src/script/render/draw.js
index f5033c6ad6..3a31b62fad 100644
--- a/packages/ketcher-react/src/script/render/draw.js
+++ b/packages/ketcher-react/src/script/render/draw.js
@@ -29,9 +29,11 @@ function rectangle(paper, pos, options) {
)
}
-function circle(paper, pos, options) {
- const rad = Vec2.dist(pos[0], pos[1])
- return paper.circle(pos[0].x, pos[0].y, rad)
+function ellipse(paper, pos, options) {
+ const rad = Vec2.diff(pos[1], pos[0])
+ const rx = rad.x / 2
+ const ry = rad.y / 2
+ return paper.ellipse(pos[0].x + rx, pos[0].y + ry, Math.abs(rx), Math.abs(ry))
}
function polyline(paper, pos, options) {
@@ -405,7 +407,7 @@ export default {
selectionRectangle,
selectionPolygon,
selectionLine,
- circle,
+ ellipse,
rectangle,
polyline,
line
diff --git a/packages/ketcher-react/src/script/render/restruct/resimpleobject.js b/packages/ketcher-react/src/script/render/restruct/resimpleobject.js
index 92524d960a..38f7600f8c 100644
--- a/packages/ketcher-react/src/script/render/restruct/resimpleobject.js
+++ b/packages/ketcher-react/src/script/render/restruct/resimpleobject.js
@@ -20,6 +20,7 @@ import draw from '../draw'
import util from '../util'
import scale from '../../util/scale'
import Vec2 from '../../util/vec2'
+import { SimpleObjectMode } from '../../chem/struct'
const tfx = util.tfx
@@ -41,13 +42,26 @@ ReSimpleObject.prototype.calcDistance = function (p, s) {
const pos = item.pos
switch (mode) {
- case 'circle': {
- const dist1 = Vec2.dist(point, pos[0])
- const dist2 = Vec2.dist(pos[0], pos[1])
- dist = Math.max(dist1, dist2) - Math.min(dist1, dist2)
+ case SimpleObjectMode.ellipse: {
+ const rad = Vec2.diff(pos[1], pos[0])
+ const rx = rad.x / 2
+ const ry = rad.y / 2
+ const center = Vec2.sum(pos[0], { x: rx, y: ry })
+ const pointToCenter = Vec2.diff(point, center)
+ if (rx !== 0 && ry !== 0) {
+ dist = Math.abs(
+ 1 -
+ (pointToCenter.x * pointToCenter.x) / (rx * rx) -
+ (pointToCenter.y * pointToCenter.y) / (ry * ry)
+ )
+ } else {
+ // in case rx or ry is equal to 0 we have a line as a trivial case of ellipse
+ // in such case distance need to be calculated as a distance between line and current point
+ dist = calculateDistanceToLine(pos, point)
+ }
break
}
- case 'rectangle': {
+ case SimpleObjectMode.rectangle: {
const topX = Math.min(pos[0].x, pos[1].x)
const topY = Math.min(pos[0].y, pos[1].y)
const bottomX = Math.max(pos[0].x, pos[1].x)
@@ -88,21 +102,8 @@ ReSimpleObject.prototype.calcDistance = function (p, s) {
dist = Math.min(...distances)
break
}
- case 'line': {
- if (
- (point.x < Math.min(pos[0].x, pos[1].x) ||
- point.x > Math.max(pos[0].x, pos[1].x)) &&
- (point.y < Math.min(pos[0].y, pos[1].y) ||
- point.y > Math.max(pos[0].y, pos[1].y))
- )
- dist = Math.min(Vec2.dist(pos[0], point), Vec2.dist(pos[1], point))
- else {
- const a = Vec2.dist(pos[0], pos[1])
- const b = Vec2.dist(pos[0], point)
- const c = Vec2.dist(pos[1], point)
- const per = (a + b + c) / 2
- dist = (2 / a) * Math.sqrt(per * (per - a) * (per - b) * (per - c))
- }
+ case SimpleObjectMode.line: {
+ dist = calculateDistanceToLine(pos, point)
break
}
@@ -117,6 +118,25 @@ ReSimpleObject.prototype.calcDistance = function (p, s) {
return { minDist: dist, refPoint: refPoint }
}
+function calculateDistanceToLine(pos, point) {
+ let dist
+ if (
+ (point.x < Math.min(pos[0].x, pos[1].x) ||
+ point.x > Math.max(pos[0].x, pos[1].x)) &&
+ (point.y < Math.min(pos[0].y, pos[1].y) ||
+ point.y > Math.max(pos[0].y, pos[1].y))
+ )
+ dist = Math.min(Vec2.dist(pos[0], point), Vec2.dist(pos[1], point))
+ else {
+ const a = Vec2.dist(pos[0], pos[1])
+ const b = Vec2.dist(pos[0], point)
+ const c = Vec2.dist(pos[1], point)
+ const per = (a + b + c) / 2
+ dist = (2 / a) * Math.sqrt(per * (per - a) * (per - b) * (per - c))
+ }
+ return dist
+}
+
ReSimpleObject.prototype.getReferencePointDistance = function (p) {
let dist = []
const refPoints = this.getReferencePoints()
@@ -136,19 +156,52 @@ ReSimpleObject.prototype.getReferencePointDistance = function (p) {
ReSimpleObject.prototype.getReferencePoints = function () {
const refPoints = []
switch (this.item.mode) {
- case 'circle': {
+ case SimpleObjectMode.ellipse: {
const p0 = this.item.pos[0]
- const rad = Vec2.dist(this.item.pos[0], this.item.pos[1])
+ const rad = Vec2.diff(this.item.pos[1], p0)
+ const rx = rad.x / 2
+ const ry = rad.y / 2
+ let point = { x: rx, y: 0 }
+ let angle = 0,
+ perimeter = 0,
+ curPoint
+ //calculate approximate perimeter value for first 1/4
+ while (angle <= 90) {
+ curPoint = new Vec2(
+ rx * Math.cos((angle * Math.PI) / 180),
+ ry * Math.sin((angle * Math.PI) / 180)
+ )
+ perimeter += Vec2.dist(point, curPoint)
+ point = curPoint
+ angle += 0.25
+ }
+ angle = 0
+ point = { x: rx, y: 0 }
+ let dist = 0
+ //get point value for first 1/8 of ellipse perimeter with approximation scheme
+ while (dist <= perimeter / 2) {
+ angle += 0.25
+ curPoint = new Vec2(
+ rx * Math.cos((angle * Math.PI) / 180),
+ ry * Math.sin((angle * Math.PI) / 180)
+ )
+ dist += Vec2.dist(point, curPoint)
+ point = curPoint
+ }
refPoints.push(
- new Vec2(p0.x - rad, p0.y),
- new Vec2(p0.x, p0.y - rad),
- new Vec2(p0.x + rad, p0.y),
- new Vec2(p0.x, p0.y + rad)
+ new Vec2(p0.x + rx, p0.y),
+ new Vec2(p0.x, p0.y + ry),
+ new Vec2(p0.x + 2 * rx, p0.y + ry),
+ new Vec2(p0.x + rx, p0.y + 2 * ry),
+ new Vec2(p0.x + rx + point.x, p0.y + ry + point.y),
+ new Vec2(p0.x + rx + point.x, p0.y + ry - point.y),
+ new Vec2(p0.x + rx - point.x, p0.y + ry + point.y),
+ new Vec2(p0.x + rx - point.x, p0.y + ry - point.y)
)
break
}
- case 'rectangle': {
+ case SimpleObjectMode.rectangle: {
const p0 = new Vec2(
tfx(Math.min(this.item.pos[0].x, this.item.pos[1].x)),
tfx(Math.min(this.item.pos[0].y, this.item.pos[1].y))
@@ -168,7 +221,7 @@ ReSimpleObject.prototype.getReferencePoints = function () {
)
break
}
- case 'line': {
+ case SimpleObjectMode.line: {
this.item.pos.forEach(i => refPoints.push(i))
break
}
@@ -192,16 +245,31 @@ ReSimpleObject.prototype.highlightPath = function (render) {
//TODO: It seems that inheritance will be the better approach here
switch (this.item.mode) {
- case 'circle': {
- const rad = Vec2.dist(point[0], point[1])
+ case SimpleObjectMode.ellipse: {
+ const rad = Vec2.diff(point[1], point[0])
+ const rx = rad.x / 2
+ const ry = rad.y / 2
path.push(
- render.paper.circle(point[0].x, point[0].y, rad + s / 8),
- render.paper.circle(point[0].x, point[0].y, rad - s / 8)
+ render.paper.ellipse(
+ tfx(point[0].x + rx),
+ tfx(point[0].y + ry),
+ tfx(Math.abs(rx) + s / 8),
+ tfx(Math.abs(ry) + s / 8)
+ )
)
+ if (Math.abs(rx) - s / 8 > 0 && Math.abs(ry) - s / 8 > 0)
+ path.push(
+ render.paper.ellipse(
+ tfx(point[0].x + rx),
+ tfx(point[0].y + ry),
+ tfx(Math.abs(rx) - s / 8),
+ tfx(Math.abs(ry) - s / 8)
+ )
+ )
break
}
- case 'rectangle': {
+ case SimpleObjectMode.rectangle: {
path.push(
render.paper.rect(
tfx(Math.min(point[0].x, point[1].x) - s / 8),
@@ -238,7 +306,7 @@ ReSimpleObject.prototype.highlightPath = function (render) {
break
}
- case 'line': {
+ case SimpleObjectMode.line: {
//TODO: reuse this code for polyline
const poly = []
@@ -351,15 +419,15 @@ ReSimpleObject.prototype.show = function (restruct, id, options) {
function generatePath(mode, paper, pos, options) {
let path = null
switch (mode) {
- case 'circle': {
- path = draw.circle(paper, pos, options)
+ case SimpleObjectMode.ellipse: {
+ path = draw.ellipse(paper, pos, options)
break
}
- case 'rectangle': {
+ case SimpleObjectMode.rectangle: {
path = draw.rectangle(paper, pos, options)
break
}
- case 'line': {
+ case SimpleObjectMode.line: {
path = draw.line(paper, pos, options)
break
}
diff --git a/packages/ketcher-react/src/script/ui/action/tools.js b/packages/ketcher-react/src/script/ui/action/tools.js
index 6c3e1dd68c..98f5d0c652 100644
--- a/packages/ketcher-react/src/script/ui/action/tools.js
+++ b/packages/ketcher-react/src/script/ui/action/tools.js
@@ -16,6 +16,7 @@
import { bond as bondSchema } from '../data/schema/struct-schema'
import { toBondType } from '../data/convert/structconv'
+import { SimpleObjectMode } from '../../chem/struct'
const toolActions = {
'select-lasso': {
@@ -113,17 +114,17 @@ const toolActions = {
title: 'Attachment Point Tool',
action: { tool: 'apoint' }
},
- 'shape-circle': {
- title: 'Shape Circle',
- action: { tool: 'simpleobject', opts: 'circle' }
+ 'shape-ellipse': {
+ title: 'Shape Ellipse',
+ action: { tool: 'simpleobject', opts: SimpleObjectMode.ellipse }
},
'shape-rectangle': {
title: 'Shape Rectangle',
- action: { tool: 'simpleobject', opts: 'rectangle' }
+ action: { tool: 'simpleobject', opts: SimpleObjectMode.rectangle }
},
'shape-line': {
title: 'Shape Line',
- action: { tool: 'simpleobject', opts: 'line' }
+ action: { tool: 'simpleobject', opts: SimpleObjectMode.line }
}
}
diff --git a/packages/ketcher-react/src/script/ui/views/toolbar.jsx b/packages/ketcher-react/src/script/ui/views/toolbar.jsx
index e6ce6854c4..ccb5c3b8c4 100644
--- a/packages/ketcher-react/src/script/ui/views/toolbar.jsx
+++ b/packages/ketcher-react/src/script/ui/views/toolbar.jsx
@@ -218,7 +218,7 @@ function initToolbar() {
if (!global?.ketcher?.standalone)
toolboxItems.push({
id: 'shape',
- menu: ['shape-circle', 'shape-rectangle', 'shape-line']
+ menu: ['shape-ellipse', 'shape-rectangle', 'shape-line']
})
return [