Created
April 18, 2021 10:11
-
-
Save smic/75d2e89e21710116bfdb325df7e33be7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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