moleditpy 1.2.1__tar.gz → 1.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {moleditpy-1.2.1 → moleditpy-1.2.2}/PKG-INFO +1 -1
- {moleditpy-1.2.1 → moleditpy-1.2.2}/pyproject.toml +1 -1
- {moleditpy-1.2.1 → moleditpy-1.2.2}/src/moleditpy/main.py +217 -39
- {moleditpy-1.2.1 → moleditpy-1.2.2}/src/moleditpy.egg-info/PKG-INFO +1 -1
- {moleditpy-1.2.1 → moleditpy-1.2.2}/README.md +0 -0
- {moleditpy-1.2.1 → moleditpy-1.2.2}/setup.cfg +0 -0
- {moleditpy-1.2.1 → moleditpy-1.2.2}/src/moleditpy/__init__.py +0 -0
- {moleditpy-1.2.1 → moleditpy-1.2.2}/src/moleditpy/assets/icon.ico +0 -0
- {moleditpy-1.2.1 → moleditpy-1.2.2}/src/moleditpy/assets/icon.png +0 -0
- {moleditpy-1.2.1 → moleditpy-1.2.2}/src/moleditpy.egg-info/SOURCES.txt +0 -0
- {moleditpy-1.2.1 → moleditpy-1.2.2}/src/moleditpy.egg-info/dependency_links.txt +0 -0
- {moleditpy-1.2.1 → moleditpy-1.2.2}/src/moleditpy.egg-info/entry_points.txt +0 -0
- {moleditpy-1.2.1 → moleditpy-1.2.2}/src/moleditpy.egg-info/requires.txt +0 -0
- {moleditpy-1.2.1 → moleditpy-1.2.2}/src/moleditpy.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: moleditpy
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: A cross-platform, simple, and intuitive molecular structure editor built in Python. It allows 2D molecular drawing and 3D structure visualization. It supports exporting structure files for input to DFT calculation software.
|
|
5
5
|
Author-email: HiroYokoyama <titech.yoko.hiro@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/HiroYokoyama/python_molecular_editor
|
|
@@ -11,7 +11,7 @@ DOI 10.5281/zenodo.17268532
|
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
#Version
|
|
14
|
-
VERSION = '1.2.
|
|
14
|
+
VERSION = '1.2.2'
|
|
15
15
|
|
|
16
16
|
print("-----------------------------------------------------")
|
|
17
17
|
print("MoleditPy — A Python-based molecular editing software")
|
|
@@ -39,7 +39,7 @@ from PyQt6.QtWidgets import (
|
|
|
39
39
|
)
|
|
40
40
|
from PyQt6.QtGui import (
|
|
41
41
|
QPen, QBrush, QColor, QPainter, QAction, QActionGroup, QFont, QPolygonF,
|
|
42
|
-
QPainterPath, QPainterPathStroker, QFontMetrics, QKeySequence, QTransform, QCursor, QPixmap, QIcon, QShortcut, QDesktopServices
|
|
42
|
+
QPainterPath, QPainterPathStroker, QFontMetrics, QFontMetricsF, QKeySequence, QTransform, QCursor, QPixmap, QIcon, QShortcut, QDesktopServices
|
|
43
43
|
)
|
|
44
44
|
from PyQt6.QtCore import Qt, QPointF, QRectF, QLineF, QObject, QThread, pyqtSignal, QEvent, QMimeData, QByteArray, QUrl
|
|
45
45
|
|
|
@@ -257,16 +257,81 @@ class AtomItem(QGraphicsItem):
|
|
|
257
257
|
super().__init__()
|
|
258
258
|
self.atom_id, self.symbol, self.charge, self.radical, self.bonds, self.chiral_label = atom_id, symbol, charge, radical, [], None
|
|
259
259
|
self.setPos(pos)
|
|
260
|
+
self.implicit_h_count = 0
|
|
260
261
|
self.setFlags(QGraphicsItem.GraphicsItemFlag.ItemIsMovable | QGraphicsItem.GraphicsItemFlag.ItemIsSelectable)
|
|
261
262
|
self.setZValue(1); self.font = QFont("Arial", 20, QFont.Weight.Bold); self.update_style()
|
|
262
263
|
self.setAcceptHoverEvents(True)
|
|
263
264
|
self.hovered = False
|
|
264
265
|
self.has_problem = False
|
|
265
|
-
|
|
266
|
+
|
|
266
267
|
def boundingRect(self):
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
268
|
+
# --- paint()メソッドと完全に同じロジックでテキストの位置とサイズを計算 ---
|
|
269
|
+
font = QFont("Arial", 20, QFont.Weight.Bold)
|
|
270
|
+
fm = QFontMetricsF(font)
|
|
271
|
+
|
|
272
|
+
hydrogen_part = ""
|
|
273
|
+
if self.implicit_h_count > 0:
|
|
274
|
+
is_skeletal_carbon = (self.symbol == 'C' and len(self.bonds) > 0)
|
|
275
|
+
if not is_skeletal_carbon:
|
|
276
|
+
hydrogen_part = "H"
|
|
277
|
+
if self.implicit_h_count > 1:
|
|
278
|
+
subscript_map = str.maketrans("0123456789", "₀₁₂₃₄₅₆₇₈₉")
|
|
279
|
+
hydrogen_part += str(self.implicit_h_count).translate(subscript_map)
|
|
280
|
+
|
|
281
|
+
flip_text = False
|
|
282
|
+
if hydrogen_part and self.bonds:
|
|
283
|
+
my_pos_x = self.pos().x()
|
|
284
|
+
total_dx = sum((b.atom2.pos().x() if b.atom1 is self else b.atom1.pos().x()) - my_pos_x for b in self.bonds)
|
|
285
|
+
if total_dx > 0:
|
|
286
|
+
flip_text = True
|
|
287
|
+
|
|
288
|
+
if flip_text:
|
|
289
|
+
display_text = hydrogen_part + self.symbol
|
|
290
|
+
else:
|
|
291
|
+
display_text = self.symbol + hydrogen_part
|
|
292
|
+
|
|
293
|
+
text_rect = fm.boundingRect(display_text)
|
|
294
|
+
text_rect.adjust(-2, -2, 2, 2)
|
|
295
|
+
if hydrogen_part:
|
|
296
|
+
symbol_rect = fm.boundingRect(self.symbol)
|
|
297
|
+
if flip_text:
|
|
298
|
+
offset_x = symbol_rect.width() // 2
|
|
299
|
+
text_rect.moveTo(offset_x - text_rect.width(), -text_rect.height() / 2)
|
|
300
|
+
else:
|
|
301
|
+
offset_x = -symbol_rect.width() // 2
|
|
302
|
+
text_rect.moveTo(offset_x, -text_rect.height() / 2)
|
|
303
|
+
else:
|
|
304
|
+
text_rect.moveCenter(QPointF(0, 0))
|
|
305
|
+
|
|
306
|
+
# 1. paint()で描画される背景の矩形(bg_rect)を計算する
|
|
307
|
+
bg_rect = text_rect.adjusted(-5, -8, 5, 8)
|
|
308
|
+
|
|
309
|
+
# 2. このbg_rectを基準として全体の描画領域を構築する
|
|
310
|
+
full_visual_rect = QRectF(bg_rect)
|
|
311
|
+
|
|
312
|
+
# 電荷記号の領域を計算に含める
|
|
313
|
+
if self.charge != 0:
|
|
314
|
+
if self.charge == 1: charge_str = "+"
|
|
315
|
+
elif self.charge == -1: charge_str = "-"
|
|
316
|
+
else: charge_str = f"{self.charge:+}"
|
|
317
|
+
charge_font = QFont("Arial", 12, QFont.Weight.Bold)
|
|
318
|
+
charge_fm = QFontMetricsF(charge_font)
|
|
319
|
+
charge_rect = charge_fm.boundingRect(charge_str)
|
|
320
|
+
|
|
321
|
+
if flip_text:
|
|
322
|
+
charge_pos = QPointF(text_rect.left() - charge_rect.width() - 2, text_rect.top())
|
|
323
|
+
else:
|
|
324
|
+
charge_pos = QPointF(text_rect.right() + 2, text_rect.top())
|
|
325
|
+
charge_rect.moveTopLeft(charge_pos)
|
|
326
|
+
full_visual_rect = full_visual_rect.united(charge_rect)
|
|
327
|
+
|
|
328
|
+
# ラジカル記号の領域を計算に含める
|
|
329
|
+
if self.radical > 0:
|
|
330
|
+
radical_area = QRectF(text_rect.center().x() - 8, text_rect.top() - 8, 16, 8)
|
|
331
|
+
full_visual_rect = full_visual_rect.united(radical_area)
|
|
332
|
+
|
|
333
|
+
# 3. 選択ハイライト等のための最終的なマージンを追加する
|
|
334
|
+
return full_visual_rect.adjusted(-3, -3, 3, 3)
|
|
270
335
|
|
|
271
336
|
def shape(self):
|
|
272
337
|
scene = self.scene()
|
|
@@ -287,34 +352,85 @@ class AtomItem(QGraphicsItem):
|
|
|
287
352
|
path.addEllipse(QPointF(0, 0), scene_radius, scene_radius)
|
|
288
353
|
return path
|
|
289
354
|
|
|
355
|
+
# In class AtomItem:
|
|
356
|
+
|
|
290
357
|
def paint(self, painter, option, widget):
|
|
291
358
|
color = CPK_COLORS.get(self.symbol, CPK_COLORS['DEFAULT'])
|
|
292
359
|
if self.is_visible:
|
|
293
|
-
# 1.
|
|
360
|
+
# 1. 描画の準備
|
|
294
361
|
painter.setFont(self.font)
|
|
295
362
|
fm = painter.fontMetrics()
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
363
|
+
|
|
364
|
+
# --- 水素部分のテキストを作成 ---
|
|
365
|
+
hydrogen_part = ""
|
|
366
|
+
if self.implicit_h_count > 0:
|
|
367
|
+
is_skeletal_carbon = (self.symbol == 'C' and len(self.bonds) > 0)
|
|
368
|
+
if not is_skeletal_carbon:
|
|
369
|
+
hydrogen_part = "H"
|
|
370
|
+
if self.implicit_h_count > 1:
|
|
371
|
+
subscript_map = str.maketrans("0123456789", "₀₁₂₃₄₅₆₇₈₉")
|
|
372
|
+
hydrogen_part += str(self.implicit_h_count).translate(subscript_map)
|
|
373
|
+
|
|
374
|
+
# --- テキストを反転させるか決定 ---
|
|
375
|
+
flip_text = False
|
|
376
|
+
# 水素ラベルがあり、結合が1本以上ある場合のみ反転を考慮
|
|
377
|
+
if hydrogen_part and self.bonds:
|
|
378
|
+
|
|
379
|
+
# 相対的なX座標で、結合が左右どちらに偏っているか判定
|
|
380
|
+
my_pos_x = self.pos().x()
|
|
381
|
+
total_dx = 0
|
|
382
|
+
for bond in self.bonds:
|
|
383
|
+
other_atom = bond.atom1 if bond.atom2 is self else bond.atom2
|
|
384
|
+
total_dx += (other_atom.pos().x() - my_pos_x)
|
|
385
|
+
|
|
386
|
+
# 結合が主に右側にある場合はテキストを反転させる
|
|
387
|
+
if total_dx > 0:
|
|
388
|
+
flip_text = True
|
|
389
|
+
|
|
390
|
+
# --- 表示テキストとアライメントを最終決定 ---
|
|
391
|
+
if flip_text:
|
|
392
|
+
display_text = hydrogen_part + self.symbol
|
|
393
|
+
alignment_flag = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
|
|
394
|
+
else:
|
|
395
|
+
display_text = self.symbol + hydrogen_part
|
|
396
|
+
alignment_flag = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter
|
|
397
|
+
|
|
398
|
+
text_rect = fm.boundingRect(display_text)
|
|
399
|
+
text_rect.adjust(-2, -2, 2, 2)
|
|
400
|
+
symbol_rect = fm.boundingRect(self.symbol) # 主元素のみの幅を計算
|
|
401
|
+
|
|
402
|
+
# --- テキストの描画位置を決定 ---
|
|
403
|
+
# 水素ラベルがない場合 (従来通り中央揃え)
|
|
404
|
+
if not hydrogen_part:
|
|
405
|
+
alignment_flag = Qt.AlignmentFlag.AlignCenter
|
|
406
|
+
text_rect.moveCenter(QPointF(0, 0).toPoint())
|
|
407
|
+
# 水素ラベルがあり、反転する場合 (右揃え)
|
|
408
|
+
elif flip_text:
|
|
409
|
+
# 主元素の中心が原子の中心に来るように、矩形の右端を調整
|
|
410
|
+
offset_x = symbol_rect.width() // 2
|
|
411
|
+
text_rect.moveTo(offset_x - text_rect.width(), -text_rect.height() // 2)
|
|
412
|
+
# 水素ラベルがあり、反転しない場合 (左揃え)
|
|
413
|
+
else:
|
|
414
|
+
# 主元素の中心が原子の中心に来るように、矩形の左端を調整
|
|
415
|
+
offset_x = -symbol_rect.width() // 2
|
|
416
|
+
text_rect.moveTo(offset_x, -text_rect.height() // 2)
|
|
417
|
+
|
|
418
|
+
# 2. 原子記号の背景を白で塗りつぶす
|
|
300
419
|
if self.scene():
|
|
301
420
|
bg_brush = self.scene().backgroundBrush()
|
|
302
|
-
bg_rect = text_rect.adjusted(-
|
|
421
|
+
bg_rect = text_rect.adjusted(-5, -8, 5, 8)
|
|
303
422
|
painter.setBrush(bg_brush)
|
|
304
423
|
painter.setPen(Qt.PenStyle.NoPen)
|
|
305
|
-
painter.
|
|
424
|
+
painter.drawEllipse(bg_rect)
|
|
306
425
|
|
|
307
426
|
# 3. 原子記号自体を描画
|
|
308
|
-
# 水素だけ黒文字、それ以外は CPK カラー
|
|
309
427
|
if self.symbol == 'H':
|
|
310
428
|
painter.setPen(QPen(Qt.GlobalColor.black))
|
|
311
429
|
else:
|
|
312
430
|
painter.setPen(QPen(color))
|
|
313
|
-
painter.drawText(text_rect,
|
|
314
|
-
|
|
431
|
+
painter.drawText(text_rect, int(alignment_flag), display_text)
|
|
315
432
|
|
|
316
|
-
# ---
|
|
317
|
-
# 電荷の描画
|
|
433
|
+
# --- 電荷とラジカルの描画 ---
|
|
318
434
|
if self.charge != 0:
|
|
319
435
|
if self.charge == 1: charge_str = "+"
|
|
320
436
|
elif self.charge == -1: charge_str = "-"
|
|
@@ -322,11 +438,14 @@ class AtomItem(QGraphicsItem):
|
|
|
322
438
|
charge_font = QFont("Arial", 12, QFont.Weight.Bold)
|
|
323
439
|
painter.setFont(charge_font)
|
|
324
440
|
charge_rect = painter.fontMetrics().boundingRect(charge_str)
|
|
325
|
-
|
|
441
|
+
# 電荷の位置も反転に対応
|
|
442
|
+
if flip_text:
|
|
443
|
+
charge_pos = QPointF(text_rect.left() - charge_rect.width() -2, text_rect.top() + charge_rect.height() - 2)
|
|
444
|
+
else:
|
|
445
|
+
charge_pos = QPointF(text_rect.right() + 2, text_rect.top() + charge_rect.height() - 2)
|
|
326
446
|
painter.setPen(Qt.GlobalColor.black)
|
|
327
447
|
painter.drawText(charge_pos, charge_str)
|
|
328
448
|
|
|
329
|
-
# ラジカルの描画
|
|
330
449
|
if self.radical > 0:
|
|
331
450
|
painter.setBrush(QBrush(Qt.GlobalColor.black))
|
|
332
451
|
painter.setPen(Qt.PenStyle.NoPen)
|
|
@@ -337,20 +456,18 @@ class AtomItem(QGraphicsItem):
|
|
|
337
456
|
painter.drawEllipse(QPointF(text_rect.center().x() - 5, radical_pos_y), 3, 3)
|
|
338
457
|
painter.drawEllipse(QPointF(text_rect.center().x() + 5, radical_pos_y), 3, 3)
|
|
339
458
|
|
|
340
|
-
|
|
459
|
+
|
|
460
|
+
# --- 選択時のハイライトなど ---
|
|
341
461
|
if self.has_problem:
|
|
342
|
-
# 優先度1: 化学的な問題がある場合は赤枠で強制的にハイライト
|
|
343
462
|
painter.setBrush(Qt.BrushStyle.NoBrush)
|
|
344
|
-
painter.setPen(QPen(QColor(255, 0, 0, 200), 4))
|
|
463
|
+
painter.setPen(QPen(QColor(255, 0, 0, 200), 4))
|
|
345
464
|
painter.drawRect(self.boundingRect())
|
|
346
|
-
|
|
347
465
|
elif self.isSelected():
|
|
348
466
|
painter.setBrush(Qt.BrushStyle.NoBrush)
|
|
349
467
|
painter.setPen(QPen(QColor(0, 100, 255), 3))
|
|
350
468
|
painter.drawRect(self.boundingRect())
|
|
351
|
-
|
|
352
469
|
if (not self.isSelected()) and getattr(self, 'hovered', False):
|
|
353
|
-
pen = QPen(QColor(144, 238, 144, 200), 3)
|
|
470
|
+
pen = QPen(QColor(144, 238, 144, 200), 3)
|
|
354
471
|
pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin)
|
|
355
472
|
painter.setBrush(Qt.BrushStyle.NoBrush)
|
|
356
473
|
painter.setPen(pen)
|
|
@@ -402,7 +519,7 @@ class BondItem(QGraphicsItem):
|
|
|
402
519
|
try: line = self.get_line_in_local_coords()
|
|
403
520
|
except Exception: line = QLineF(0, 0, 0, 0)
|
|
404
521
|
bond_offset = globals().get('BOND_OFFSET', 2)
|
|
405
|
-
extra = (getattr(self, 'order', 1) - 1) * bond_offset +
|
|
522
|
+
extra = (getattr(self, 'order', 1) - 1) * bond_offset + 20 # extraを拡大
|
|
406
523
|
return QRectF(line.p1(), line.p2()).normalized().adjusted(-extra, -extra, extra, extra)
|
|
407
524
|
|
|
408
525
|
def shape(self):
|
|
@@ -439,13 +556,14 @@ class BondItem(QGraphicsItem):
|
|
|
439
556
|
line = self.get_line_in_local_coords()
|
|
440
557
|
if line.length() == 0: return
|
|
441
558
|
|
|
442
|
-
# --- 1.
|
|
443
|
-
current_pen = self.pen
|
|
559
|
+
# --- 1. 選択状態に応じてペンとブラシを準備 ---
|
|
444
560
|
if self.isSelected():
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
561
|
+
selection_color = QColor("blue")
|
|
562
|
+
painter.setPen(QPen(selection_color, 3))
|
|
563
|
+
painter.setBrush(QBrush(selection_color))
|
|
564
|
+
else:
|
|
565
|
+
painter.setPen(self.pen)
|
|
566
|
+
painter.setBrush(QBrush(Qt.GlobalColor.black))
|
|
449
567
|
|
|
450
568
|
# --- 立体化学 (Wedge/Dash) の描画 ---
|
|
451
569
|
if self.order == 1 and self.stereo in [1, 2]:
|
|
@@ -455,16 +573,16 @@ class BondItem(QGraphicsItem):
|
|
|
455
573
|
p2 = line.p2() - vec.p2() * 5
|
|
456
574
|
|
|
457
575
|
if self.stereo == 1: # Wedge (くさび形)
|
|
458
|
-
if self.isSelected():
|
|
459
|
-
painter.setBrush(Qt.BrushStyle.NoBrush) # 選択時は塗りつぶさず枠線にする
|
|
460
576
|
offset = QPointF(normal.dx(), normal.dy()) * 6.0
|
|
461
577
|
poly = QPolygonF([p1, p2 + offset, p2 - offset])
|
|
462
578
|
painter.drawPolygon(poly)
|
|
463
579
|
|
|
464
580
|
elif self.stereo == 2: # Dash (破線)
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
581
|
+
if not self.isSelected():
|
|
582
|
+
pen = painter.pen()
|
|
583
|
+
pen.setWidthF(2.5)
|
|
584
|
+
painter.setPen(pen)
|
|
585
|
+
|
|
468
586
|
num_dashes = 8
|
|
469
587
|
for i in range(num_dashes + 1):
|
|
470
588
|
t = i / num_dashes
|
|
@@ -608,12 +726,14 @@ class MoleculeScene(QGraphicsScene):
|
|
|
608
726
|
if isinstance(item, AtomItem):
|
|
609
727
|
# ラジカルモードの場合、ラジカルを0にする
|
|
610
728
|
if self.mode == 'radical' and item.radical != 0:
|
|
729
|
+
item.prepareGeometryChange()
|
|
611
730
|
item.radical = 0
|
|
612
731
|
self.data.atoms[item.atom_id]['radical'] = 0
|
|
613
732
|
item.update_style()
|
|
614
733
|
data_changed = True
|
|
615
734
|
# 電荷モードの場合、電荷を0にする
|
|
616
735
|
elif self.mode in ['charge_plus', 'charge_minus'] and item.charge != 0:
|
|
736
|
+
item.prepareGeometryChange()
|
|
617
737
|
item.charge = 0
|
|
618
738
|
self.data.atoms[item.atom_id]['charge'] = 0
|
|
619
739
|
item.update_style()
|
|
@@ -719,6 +839,7 @@ class MoleculeScene(QGraphicsScene):
|
|
|
719
839
|
# 1. 特殊モード(ラジカル/電荷)の処理
|
|
720
840
|
if (self.mode == 'radical') and is_click and isinstance(released_item, AtomItem):
|
|
721
841
|
atom = released_item
|
|
842
|
+
atom.prepareGeometryChange()
|
|
722
843
|
# ラジカルの状態をトグル (0 -> 1 -> 2 -> 0)
|
|
723
844
|
atom.radical = (atom.radical + 1) % 3
|
|
724
845
|
self.data.atoms[atom.atom_id]['radical'] = atom.radical
|
|
@@ -726,6 +847,7 @@ class MoleculeScene(QGraphicsScene):
|
|
|
726
847
|
self.data_changed_in_event = True
|
|
727
848
|
elif (self.mode == 'charge_plus' or self.mode == 'charge_minus') and is_click and isinstance(released_item, AtomItem):
|
|
728
849
|
atom = released_item
|
|
850
|
+
atom.prepareGeometryChange()
|
|
729
851
|
delta = 1 if self.mode == 'charge_plus' else -1
|
|
730
852
|
atom.charge += delta
|
|
731
853
|
self.data.atoms[atom.atom_id]['charge'] = atom.charge
|
|
@@ -1268,6 +1390,7 @@ class MoleculeScene(QGraphicsScene):
|
|
|
1268
1390
|
if target_atoms:
|
|
1269
1391
|
for atom in target_atoms:
|
|
1270
1392
|
# ラジカルの状態をトグル (0 -> 1 -> 2 -> 0)
|
|
1393
|
+
atom.prepareGeometryChange()
|
|
1271
1394
|
atom.radical = (atom.radical + 1) % 3
|
|
1272
1395
|
self.data.atoms[atom.atom_id]['radical'] = atom.radical
|
|
1273
1396
|
atom.update_style()
|
|
@@ -1287,6 +1410,7 @@ class MoleculeScene(QGraphicsScene):
|
|
|
1287
1410
|
if target_atoms:
|
|
1288
1411
|
delta = 1 if key == Qt.Key.Key_Plus else -1
|
|
1289
1412
|
for atom in target_atoms:
|
|
1413
|
+
atom.prepareGeometryChange()
|
|
1290
1414
|
atom.charge += delta
|
|
1291
1415
|
self.data.atoms[atom.atom_id]['charge'] = atom.charge
|
|
1292
1416
|
atom.update_style()
|
|
@@ -1303,9 +1427,22 @@ class MoleculeScene(QGraphicsScene):
|
|
|
1303
1427
|
new_symbol = self.key_to_symbol_map_shift[key]
|
|
1304
1428
|
|
|
1305
1429
|
if new_symbol and item_at_cursor.symbol != new_symbol:
|
|
1430
|
+
item_at_cursor.prepareGeometryChange() # <<<<<< この行を追加
|
|
1431
|
+
|
|
1306
1432
|
item_at_cursor.symbol = new_symbol
|
|
1307
1433
|
self.data.atoms[item_at_cursor.atom_id]['symbol'] = new_symbol
|
|
1308
1434
|
item_at_cursor.update_style()
|
|
1435
|
+
|
|
1436
|
+
|
|
1437
|
+
atoms_to_update = {item_at_cursor}
|
|
1438
|
+
for bond in item_at_cursor.bonds:
|
|
1439
|
+
bond.update()
|
|
1440
|
+
other_atom = bond.atom1 if bond.atom2 is item_at_cursor else bond.atom2
|
|
1441
|
+
atoms_to_update.add(other_atom)
|
|
1442
|
+
|
|
1443
|
+
for atom in atoms_to_update:
|
|
1444
|
+
atom.update_style()
|
|
1445
|
+
|
|
1309
1446
|
self.window.push_undo_state()
|
|
1310
1447
|
event.accept()
|
|
1311
1448
|
return
|
|
@@ -2743,6 +2880,7 @@ class MainWindow(QMainWindow):
|
|
|
2743
2880
|
self.set_mode('select')
|
|
2744
2881
|
if 'select' in self.mode_actions:
|
|
2745
2882
|
self.mode_actions['select'].setChecked(True)
|
|
2883
|
+
|
|
2746
2884
|
def trigger_conversion(self):
|
|
2747
2885
|
self.scene.clear_all_problem_flags()
|
|
2748
2886
|
mol = self.data.to_rdkit_mol()
|
|
@@ -2943,6 +3081,7 @@ class MainWindow(QMainWindow):
|
|
|
2943
3081
|
self.export_button.setEnabled(False)
|
|
2944
3082
|
self.edit_3d_action.setEnabled(False)
|
|
2945
3083
|
|
|
3084
|
+
self.update_implicit_hydrogens()
|
|
2946
3085
|
self.update_chiral_labels()
|
|
2947
3086
|
|
|
2948
3087
|
|
|
@@ -2970,7 +3109,8 @@ class MainWindow(QMainWindow):
|
|
|
2970
3109
|
state = self.get_current_state()
|
|
2971
3110
|
self.undo_stack.append(state)
|
|
2972
3111
|
self.redo_stack.clear()
|
|
2973
|
-
|
|
3112
|
+
|
|
3113
|
+
self.update_implicit_hydrogens()
|
|
2974
3114
|
self.update_realtime_info()
|
|
2975
3115
|
self.update_undo_redo_actions()
|
|
2976
3116
|
|
|
@@ -3071,6 +3211,44 @@ class MainWindow(QMainWindow):
|
|
|
3071
3211
|
if push_to_undo:
|
|
3072
3212
|
self.push_undo_state()
|
|
3073
3213
|
|
|
3214
|
+
def update_implicit_hydrogens(self):
|
|
3215
|
+
"""現在の2D構造に基づいて各原子の暗黙の水素数を計算し、AtomItemに反映する"""
|
|
3216
|
+
if not self.data.atoms:
|
|
3217
|
+
return
|
|
3218
|
+
|
|
3219
|
+
try:
|
|
3220
|
+
mol = self.data.to_rdkit_mol()
|
|
3221
|
+
if mol is None:
|
|
3222
|
+
# 構造が不正な場合、全原子の水素カウントを0に戻して再描画
|
|
3223
|
+
for atom_data in self.data.atoms.values():
|
|
3224
|
+
if atom_data.get('item') and atom_data['item'].implicit_h_count != 0:
|
|
3225
|
+
atom_data['item'].implicit_h_count = 0
|
|
3226
|
+
atom_data['item'].update()
|
|
3227
|
+
return
|
|
3228
|
+
|
|
3229
|
+
mol.UpdatePropertyCache(strict=False)
|
|
3230
|
+
|
|
3231
|
+
items_to_update = []
|
|
3232
|
+
for atom in mol.GetAtoms():
|
|
3233
|
+
if atom.HasProp("_original_atom_id"):
|
|
3234
|
+
original_id = atom.GetIntProp("_original_atom_id")
|
|
3235
|
+
if original_id in self.data.atoms:
|
|
3236
|
+
item = self.data.atoms[original_id].get('item')
|
|
3237
|
+
if item:
|
|
3238
|
+
h_count = atom.GetNumImplicitHs()
|
|
3239
|
+
if item.implicit_h_count != h_count:
|
|
3240
|
+
item.prepareGeometryChange()
|
|
3241
|
+
item.implicit_h_count = h_count
|
|
3242
|
+
items_to_update.append(item)
|
|
3243
|
+
|
|
3244
|
+
# カウントが変更されたアイテムのみ再描画をトリガー
|
|
3245
|
+
for item in items_to_update:
|
|
3246
|
+
item.update()
|
|
3247
|
+
except Exception:
|
|
3248
|
+
# 編集中に一時的に発生するエラーなどで計算が失敗してもアプリは継続
|
|
3249
|
+
pass
|
|
3250
|
+
|
|
3251
|
+
|
|
3074
3252
|
def import_smiles_dialog(self):
|
|
3075
3253
|
"""ユーザーにSMILES文字列の入力を促すダイアログを表示する"""
|
|
3076
3254
|
smiles, ok = QInputDialog.getText(self, "Import SMILES", "Enter SMILES string:")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: moleditpy
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: A cross-platform, simple, and intuitive molecular structure editor built in Python. It allows 2D molecular drawing and 3D structure visualization. It supports exporting structure files for input to DFT calculation software.
|
|
5
5
|
Author-email: HiroYokoyama <titech.yoko.hiro@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/HiroYokoyama/python_molecular_editor
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|