Skip to content

Commit

Permalink
Merge branch 'master' into #1990-functional-group-does-not-connect-wi…
Browse files Browse the repository at this point in the history
…th-another-functional-group-on-clickdrag
  • Loading branch information
Stanislav Permiakov committed Feb 14, 2023
2 parents 42932dd + 30df9a0 commit 5de88ce
Show file tree
Hide file tree
Showing 10 changed files with 331 additions and 309 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export function fromItemsFuse(restruct, items) {

const connectedAtomIds = getAllConnectedAtomsIds(
restruct,
mergeMapOfAtomsToSet(items.atoms),
mergeMapOfAtomsToSet(items.bonds)
mergeMapOfItemsToSet(items.atoms),
mergeMapOfItemsToSet(items.bonds)
)

// merge single atoms
Expand Down Expand Up @@ -73,6 +73,14 @@ export function getHoverToFuse(items) {
return { map: 'merge', id: +Date.now(), items: hoverItems }
}

export function mergeMapOfItemsToSet(items: Map<number, number>): Set<number> {
const itemsSet = new Set<number>()
items.forEach((value, key) => {
itemsSet.add(value).add(key)
})
return itemsSet
}

/**
* @param struct
* @param closestMap {{
Expand Down Expand Up @@ -107,14 +115,6 @@ function closestToMerge(struct, closestMap) {
return mergeMap
}

function mergeMapOfAtomsToSet(items: Map<number, number>): Set<number> {
const itemsSet = new Set<number>()
items.forEach((value, key) => {
itemsSet.add(value).add(key)
})
return itemsSet
}

function getAllConnectedAtomsIds(restruct, atomsIds, bondsIds) {
const initialAtoms = new Set(atomsIds)
const connectedAtoms = new Set()
Expand Down
28 changes: 28 additions & 0 deletions packages/ketcher-core/src/domain/entities/struct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ export class Struct {
)
}

isSingleGroup(): boolean {
if (!this.sgroups.size || this.sgroups.size > 1) return false
const sgroup = this.sgroups.values().next().value // get sgroup from map
return this.atoms.size === sgroup.atoms.length
}

clone(
atomSet?: Pile<number> | null,
bondSet?: Pile<number> | null,
Expand Down Expand Up @@ -1064,4 +1070,26 @@ export class Struct {
}
})
}

getGroupIdFromAtomId(atomId: number): number | null {
for (const [groupId, sgroup] of Array.from(this.sgroups)) {
if (sgroup.atoms.includes(atomId)) return groupId
}
return null
}

// TODO: simplify if bonds ids ever appear in sgroup
getGroupIdFromBondId(bondId: number): number | null {
const bond = this.bonds.get(bondId)
if (!bond) return null
for (const [groupId, sgroup] of Array.from(this.sgroups)) {
if (
sgroup.atoms.includes(bond.begin) ||
sgroup.atoms.includes(bond.end)
) {
return groupId
}
}
return null
}
}
2 changes: 1 addition & 1 deletion packages/ketcher-react/src/script/editor/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ function selectStereoFlagsIfNecessary(
return stereoFlags
}

interface Selection {
export interface Selection {
atoms?: Array<number>
bonds?: Array<number>
enhancedFlags?: Array<number>
Expand Down
43 changes: 41 additions & 2 deletions packages/ketcher-react/src/script/editor/shared/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
***************************************************************************/

import { Vec2, fracAngle } from 'ketcher-core'
import { Vec2, fracAngle, Struct, FunctionalGroup } from 'ketcher-core'

import { inRange } from 'lodash'

Expand Down Expand Up @@ -59,10 +59,49 @@ function mergeBondsParams(struct1, bond1, struct2, bond2) {
return { merged, angle, scale, cross: Math.abs(degrees(angle)) > 90 }
}

/**
* Get all items IDs that do not belong to sgroups
* @param items {{ atoms?: number[]; bonds?: number[] } | null}
* @param struct {Struct}
* @returns {{ atoms: number[], bonds: number[] }}
*/
function getOnlyNonGroupItems(items, struct) {
const atoms =
items.atoms?.filter((key) => struct.getGroupIdFromAtomId(key) === null) ||
[]
const bonds =
items.bonds?.filter((key) => struct.getGroupIdFromBondId(key) === null) ||
[]

return { atoms, bonds }
}

/**
* Get all items IDs that do not belong to sgroups (except their attachment points)
* @param items {{ atoms?: number[]; bonds?: number[] } | null}
* @param struct {Struct}
* @returns {{ atoms: number[], bonds: number[] }}
*/
function getNonGroupItemsAndAttachmentPoints(items, struct) {
const atoms =
items.atoms?.filter(
(key) =>
struct.getGroupIdFromAtomId(key) === null ||
FunctionalGroup.isAttachmentPointAtom(key, struct)
) || []
const bonds =
items.bonds?.filter((key) => struct.getGroupIdFromBondId(key) === null) ||
[]

return { atoms, bonds }
}

export default {
calcAngle,
fracAngle,
calcNewAtomPos,
degrees,
mergeBondsParams
mergeBondsParams,
getOnlyNonGroupItems,
getNonGroupItemsAndAttachmentPoints
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Action, fromItemsFuse, ReStruct, setExpandSGroup } from 'ketcher-core'
import Editor from '../../Editor'
import { getGroupIdsFromItemMaps } from './getGroupIdsFromItems'

type MergeItems = {
atoms: Map<number, number>
bonds: Map<number, number>
}

export function dropAndMerge(
editor: Editor,
mergeItems: MergeItems,
action?: Action
): void {
const restruct = editor.render.ctab
const isMerging = !!mergeItems
let dropItemAction = new Action()

if (isMerging) {
const expandGroupsAction = getExpandGroupsInMergeAction(
editor.render.ctab,
mergeItems
)
dropItemAction = dropItemAction.mergeWith(expandGroupsAction)
}

dropItemAction = fromItemsFuse(restruct, mergeItems).mergeWith(dropItemAction)

if (action) {
dropItemAction = dropItemAction.mergeWith(action)
}

editor.hover(null)
if (isMerging) editor.selection(null)

if (dropItemAction?.operations.length > 0) {
editor.update(dropItemAction)
}
}

function getExpandGroupsInMergeAction(
restruct: ReStruct,
mergeItems: MergeItems
): Action {
const action = new Action()
const groupsInMerge = getGroupIdsFromItemMaps(restruct.molecule, mergeItems)
if (groupsInMerge.length) {
groupsInMerge.forEach((groupId) => {
action.mergeWith(setExpandSGroup(restruct, groupId, { expanded: true }))
})
}

return action
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { mergeMapOfItemsToSet, Struct } from 'ketcher-core'

type Items = {
atoms?: number[]
bonds?: number[]
}

function getGroupIdsFromItemArrays(struct: Struct, items?: Items): number[] {
if (!struct.sgroups.size) return []

const groupsIds = new Set<number>()

items?.atoms?.forEach((atomId) => {
const groupId = struct.getGroupIdFromAtomId(atomId)
if (groupId !== null) groupsIds.add(groupId)
})

items?.bonds?.forEach((bondId) => {
const groupId = struct.getGroupIdFromBondId(bondId)
if (groupId !== null) groupsIds.add(groupId)
})

return Array.from(groupsIds)
}

type MergeItems = {
atoms: Map<number, number>
bonds: Map<number, number>
}

function getGroupIdsFromItemMaps(
struct: Struct,
mergeMaps: MergeItems | null
): number[] {
const atoms =
mergeMaps?.atoms && Array.from(mergeMapOfItemsToSet(mergeMaps.atoms))
const bonds =
mergeMaps?.bonds && Array.from(mergeMapOfItemsToSet(mergeMaps.bonds))

return getGroupIdsFromItemArrays(struct, { atoms, bonds })
}

export { getGroupIdsFromItemArrays, getGroupIdsFromItemMaps }
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { getItemsToFuse } from 'ketcher-core'
import Editor from '../../Editor'
import utils from '../../shared/utils'

export function getMergeItems(
editor: Editor,
items: Record<string, number[]>
): Record<string, Map<unknown, unknown>> | null {
const nonGroupItemsAndAttachPoints = {
...items,
...utils.getNonGroupItemsAndAttachmentPoints(
items,
editor.render.ctab.molecule
)
}

return getItemsToFuse(editor, nonGroupItemsAndAttachPoints)
}
Loading

0 comments on commit 5de88ce

Please sign in to comment.