Skip to content

Instantly share code, notes, and snippets.

@smic
Created April 18, 2021 10:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save smic/75d2e89e21710116bfdb325df7e33be7 to your computer and use it in GitHub Desktop.
Save smic/75d2e89e21710116bfdb325df7e33be7 to your computer and use it in GitHub Desktop.
//
// SMBond+Position.swift
// SMChemSketchSwiftModel
//
// Created by Stephan Michels on 09.01.17.
// Copyright © 2017 Stephan Michels. All rights reserved.
//
import Foundation
import SMGraphics
extension SMDerivedPropertyKeys {
static let bondBondPosition = SMDerivedPropertyKey<SMBond, SMBondPosition>("bondBondPosition", function: calculateBondDoublePositon, comparator: ==)
}
extension SMBond {
public var calculatedPosition: SMBondPosition {
return self[.bondBondPosition]
}
}
// Maximum interval in which angles can vary
private let ANGLE_SIGMA: CGAngle = .radian(0.1)
private let RingSizes: [Int] = [6, 5, 7, 4, 3]
private let RingElements: [SMElement] = [.carbon, .nitrogen, .oxygen, .sulfur, .phosphorus]
private let RingSorter: (Set<SMAtom>, Set<SMAtom>) -> Bool = { (ring1, ring2) -> Bool in
let ringSize1 = ring1.count
let ringSize2 = ring2.count
if ringSize1 != ringSize2 {
for ringSize in RingSizes {
if ringSize1 == ringSize {
return true
}
if ringSize2 == ringSize {
return false
}
}
return ringSize1 < ringSize2
}
var ringBonds1: [SMBond] = []
for atom in ring1 {
for bond in atom.visibleBonds {
if let otherAtom = bond.atom(withOther: atom) {
if ring1.contains(otherAtom) {
ringBonds1.append(bond)
break
}
}
}
}
var ringBonds2: [SMBond] = []
for atom in ring2 {
for bond in atom.visibleBonds {
if let otherAtom = bond.atom(withOther: atom) {
if ring2.contains(otherAtom) {
ringBonds2.append(bond)
break
}
}
}
}
let doubleBondCount1 = ringBonds1.reduce(0, { $1.order == .double ? $0 + 1 : $0 })
let doubleBondCount2 = ringBonds2.reduce(0, { $1.order == .double ? $0 + 1 : $0 })
if doubleBondCount1 != doubleBondCount2 {
return doubleBondCount1 > doubleBondCount2
}
for element in RingElements {
let atomicNumber = UInt16(element.atomicNumber)
let elementCount1 = ring1.reduce(0, { $1.type == .element && $1.element == atomicNumber ? $0 + 1 : $0 })
let elementCount2 = ring2.reduce(0, { $1.type == .element && $1.element == atomicNumber ? $0 + 1 : $0 })
if elementCount1 != elementCount2 {
return elementCount1 > elementCount2
}
}
return false
}
private func calculateBondDoublePositon(bond: SMBond) -> SMBondPosition {
let limit = CGAngle.radianPI - ANGLE_SIGMA
let bondNeighbors = bond.bondNeighbors
var position = bond.position
if position != .auto {
return position
}
var leftRing: Set<SMAtom>? = nil
var rightRing: Set<SMAtom>? = nil
if let fragment = bond.owningFragment,
let atom1 = bond.beginAtom,
let atom2 = bond.endAtom {
if let allRings = fragment.rings {
let rings = allRings.map({ (ring: Set<SMWeakReference<SMAtom>>) -> Set<SMAtom> in Set(ring.compactMap(\.reference)) }).filter({ $0.contains(atom1) && $0.contains(atom2) })
if !rings.isEmpty {
if let left1 = bondNeighbors.left1, left1.angle < limit {
if let ring = rings.filter({ $0.contains(left1.atom) }).min(by: RingSorter) {
if let previousLeftRing = leftRing, RingSorter(ring, previousLeftRing) {
leftRing = ring
} else if leftRing == nil {
leftRing = ring
}
}
}
if let right1 = bondNeighbors.right1, right1.angle < limit {
if let ring = rings.filter({ $0.contains(right1.atom) }).min(by: RingSorter) {
if let previousRightRing = rightRing, RingSorter(ring, previousRightRing) {
rightRing = ring
} else if rightRing == nil {
rightRing = ring
}
}
}
if let left2 = bondNeighbors.left2, left2.angle < limit {
if let ring = rings.filter({ $0.contains(left2.atom) }).min(by: RingSorter) {
if let previousLeftRing = leftRing, RingSorter(ring, previousLeftRing) {
leftRing = ring
} else if leftRing == nil {
leftRing = ring
}
}
}
if let right2 = bondNeighbors.right2, right2.angle < limit {
if let ring = rings.filter({ $0.contains(right2.atom) }).min(by: RingSorter) {
if let previousRightRing = rightRing, RingSorter(ring, previousRightRing) {
rightRing = ring
} else if rightRing == nil {
rightRing = ring
}
}
}
}
}
}
let left1 = bondNeighbors.left1 != nil && bondNeighbors.left1!.angle < limit
let right1 = bondNeighbors.right1 != nil && bondNeighbors.right1!.angle < limit
let left2 = bondNeighbors.left2 != nil && bondNeighbors.left2!.angle < limit
let right2 = bondNeighbors.right2 != nil && bondNeighbors.right2!.angle < limit
let bondDisplay1 = bond.displays.first ?? .solid
if let leftRing = leftRing {
if let rightRing = rightRing {
position = RingSorter(leftRing, rightRing) ? .right : .left
} else {
position = .right
}
} else if rightRing != nil {
position = .left
} else if left1 && !right1 && ((!left2 && !right2) || (left2 && !right2) || (left2 && right2)) {
position = .right
} else if !left1 && right1 && ((!left2 && !right2) || (!left2 && right2) || (left2 && right2)) {
position = .left
} else if ((!left1 && !right1) || (left1 && !right1) || (left1 && right1)) && left2 && !right2 {
position = .right
} else if ((!left1 && !right1) || (!left1 && right1) || (left1 && right1)) && !left2 && right2 {
position = .left
} else if !left1 && !right1 && bondDisplay1 != .bold {
position = .center
} else if !left2 && !right2 && bondDisplay1 != .bold {
position = .center
} else {
position = .right
}
// check for ketenes to modify the double bond position
var neighorBonds: [SMBond] = []
if let atom = bond.beginAtom {
neighorBonds.append(contentsOf: atom.visibleBonds.filter({ $0 !== bond }))
}
if let atom = bond.endAtom {
neighorBonds.append(contentsOf: atom.visibleBonds.filter({ $0 !== bond }))
}
for neighorBond in neighorBonds where neighorBond.order == .double {
let diff = abs(SMDiffAngle(bond.vectors.angle, neighorBond.vectors.angle))
// if diametral
if diff <= ANGLE_SIGMA || diff >= .radianPI - ANGLE_SIGMA {
SMLog("Found ketene, so made double position center")
position = .center
}
}
return position
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment