MoleditPy-linux 2.3.2__py3-none-any.whl → 2.4.0__py3-none-any.whl

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.
@@ -13,7 +13,7 @@ DOI: 10.5281/zenodo.17268532
13
13
  from PyQt6.QtWidgets import QGraphicsItem
14
14
 
15
15
  from PyQt6.QtGui import (
16
- QPen, QBrush, QColor, QFont, QPainterPath, QFontMetricsF
16
+ QPen, QBrush, QColor, QFont, QPainterPath, QFontMetricsF, QPainter
17
17
  )
18
18
 
19
19
  from PyQt6.QtCore import (
@@ -45,14 +45,41 @@ class AtomItem(QGraphicsItem):
45
45
  self.setPos(pos)
46
46
  self.implicit_h_count = 0
47
47
  self.setFlags(QGraphicsItem.GraphicsItemFlag.ItemIsMovable | QGraphicsItem.GraphicsItemFlag.ItemIsSelectable)
48
- self.setZValue(1); self.font = QFont(FONT_FAMILY, FONT_SIZE_LARGE, FONT_WEIGHT_BOLD); self.update_style()
48
+ self.setZValue(1)
49
+ self.update_style()
49
50
  self.setAcceptHoverEvents(True)
50
51
  self.hovered = False
51
52
  self.has_problem = False
52
53
 
54
+
55
+ def update_style(self):
56
+ # Allow updating font preference dynamically
57
+ font_size = 20
58
+ try:
59
+ if self.scene() and self.scene().views():
60
+ win = self.scene().views()[0].window()
61
+ if win and hasattr(win, 'settings'):
62
+ font_size = win.settings.get('atom_font_size_2d', 20)
63
+ except Exception:
64
+ pass
65
+ self.font = QFont(FONT_FAMILY, font_size, FONT_WEIGHT_BOLD)
66
+ self.prepareGeometryChange()
67
+
68
+ self.is_visible = not (self.symbol == 'C' and len(self.bonds) > 0 and self.charge == 0 and self.radical == 0)
69
+ self.update()
70
+
53
71
  def boundingRect(self):
54
72
  # --- paint()メソッドと完全に同じロジックでテキストの位置とサイズを計算 ---
55
- font = QFont(FONT_FAMILY, FONT_SIZE_LARGE, FONT_WEIGHT_BOLD)
73
+ # Get dynamic font size
74
+ font_size = 20
75
+ try:
76
+ if self.scene() and self.scene().views():
77
+ win = self.scene().views()[0].window()
78
+ if win and hasattr(win, 'settings'):
79
+ font_size = win.settings.get('atom_font_size_2d', 20)
80
+ except Exception:
81
+ pass
82
+ font = QFont(FONT_FAMILY, font_size, FONT_WEIGHT_BOLD)
56
83
  fm = QFontMetricsF(font)
57
84
 
58
85
  hydrogen_part = ""
@@ -164,9 +191,21 @@ class AtomItem(QGraphicsItem):
164
191
  return path
165
192
 
166
193
  def paint(self, painter, option, widget):
194
+ # Color logic: check if we should use bond color (uniform) or CPK (element-specific)
167
195
  color = CPK_COLORS.get(self.symbol, CPK_COLORS['DEFAULT'])
196
+ try:
197
+ if self.scene() and self.scene().views():
198
+ win = self.scene().views()[0].window()
199
+ if win and hasattr(win, 'settings'):
200
+ if win.settings.get('atom_use_bond_color_2d', False):
201
+ bond_col = win.settings.get('bond_color_2d', '#222222')
202
+ color = QColor(bond_col)
203
+ except Exception:
204
+ pass
205
+
168
206
  if self.is_visible:
169
207
  # 1. 描画の準備
208
+ # Ensure correct font is used (self.font should be updated by update_style)
170
209
  painter.setFont(self.font)
171
210
  fm = painter.fontMetrics()
172
211
 
@@ -253,19 +292,29 @@ class AtomItem(QGraphicsItem):
253
292
  offset_x = -symbol_rect.width() // 2
254
293
  text_rect.moveTo(offset_x, -text_rect.height() // 2)
255
294
 
256
- # 2. 原子記号の背景を白で塗りつぶす
295
+ # 2. 原子記号の背景を処理(白で塗りつぶす か 透明なら切り抜く)
257
296
  if self.scene():
258
297
  bg_brush = self.scene().backgroundBrush()
259
298
  bg_rect = text_rect.adjusted(-5, -8, 5, 8)
260
- painter.setBrush(bg_brush)
261
- painter.setPen(Qt.PenStyle.NoPen)
262
- painter.drawEllipse(bg_rect)
299
+
300
+ if bg_brush.style() == Qt.BrushStyle.NoBrush:
301
+ # 背景が透明の場合は、CompositionMode_Clearを使って
302
+ # 重なっている結合の線を「消しゴム」のように消す
303
+ painter.save()
304
+ painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_Clear)
305
+ painter.setBrush(QColor(0, 0, 0, 255)) # 色は何でも良い(アルファが重要)
306
+ painter.setPen(Qt.PenStyle.NoPen)
307
+ painter.drawEllipse(bg_rect)
308
+ painter.restore()
309
+ else:
310
+ # 背景がある場合は、その背景色で塗りつぶす(従来通り)
311
+ painter.setBrush(bg_brush)
312
+ painter.setPen(Qt.PenStyle.NoPen)
313
+ painter.drawEllipse(bg_rect)
263
314
 
264
315
  # 3. 原子記号自体を描画
265
- if self.symbol == 'H':
266
- painter.setPen(QPen(Qt.GlobalColor.black))
267
- else:
268
- painter.setPen(QPen(color))
316
+ # Color is already determined above
317
+ painter.setPen(QPen(color))
269
318
  painter.drawText(text_rect, int(alignment_flag), display_text)
270
319
 
271
320
  # --- 電荷とラジカルの描画 ---
@@ -316,9 +365,7 @@ class AtomItem(QGraphicsItem):
316
365
  painter.setPen(pen)
317
366
  painter.drawRect(self.boundingRect())
318
367
 
319
- def update_style(self):
320
- self.is_visible = not (self.symbol == 'C' and len(self.bonds) > 0 and self.charge == 0 and self.radical == 0)
321
- self.update()
368
+
322
369
 
323
370
 
324
371
  # 約203行目 AtomItem クラス内
@@ -105,7 +105,21 @@ class BondItem(QGraphicsItem):
105
105
  line = self.get_line_in_local_coords()
106
106
  except Exception:
107
107
  line = QLineF(0, 0, 0, 0)
108
- bond_offset = globals().get('BOND_OFFSET', 2)
108
+
109
+ # Get dynamic bond offset (spacing)
110
+ bond_offset = 3.5
111
+ try:
112
+ if self.scene() and hasattr(self.scene(), 'views') and self.scene().views():
113
+ win = self.scene().views()[0].window()
114
+ if win and hasattr(win, 'settings'):
115
+ # Use specific spacing based on bond order
116
+ if getattr(self, 'order', 1) == 3:
117
+ bond_offset = win.settings.get('bond_spacing_triple_2d', 3.5)
118
+ else:
119
+ bond_offset = win.settings.get('bond_spacing_double_2d', 3.5)
120
+ except Exception:
121
+ bond_offset = globals().get('BOND_OFFSET', 3.5)
122
+
109
123
  extra = (getattr(self, 'order', 1) - 1) * bond_offset + 20
110
124
  rect = QRectF(line.p1(), line.p2()).normalized().adjusted(-extra, -extra, extra, extra)
111
125
 
@@ -142,7 +156,18 @@ class BondItem(QGraphicsItem):
142
156
  view = scene.views()[0]
143
157
  scale = view.transform().m11()
144
158
 
145
- scene_width = DESIRED_BOND_PIXEL_WIDTH / scale
159
+ # Dynamic bond width
160
+ width_2d = 2.0
161
+ try:
162
+ if view.window() and hasattr(view.window(), 'settings'):
163
+ width_2d = view.window().settings.get('bond_width_2d', 2.0)
164
+ except Exception:
165
+ pass
166
+
167
+ # Hit area should be roughly closely matched or slightly larger than visual
168
+ # Ensure minimum hit width for usability
169
+ scene_width = max(DESIRED_BOND_PIXEL_WIDTH, width_2d * 10) / scale
170
+
146
171
 
147
172
  stroker = QPainterPathStroker()
148
173
  stroker.setWidth(scene_width)
@@ -160,24 +185,44 @@ class BondItem(QGraphicsItem):
160
185
  line = self.get_line_in_local_coords()
161
186
  if line.length() == 0: return
162
187
 
163
- # --- 1. 選択状態に応じてペンとブラシを準備 ---
164
- if self.isSelected():
165
- selection_color = QColor("blue")
166
- painter.setPen(QPen(selection_color, 3))
167
- painter.setBrush(QBrush(selection_color))
168
- else:
169
- # Allow bond color override from app settings (2D color)
170
- try:
171
- sc = self.scene()
172
- if sc is not None and hasattr(sc, 'window') and sc.window is not None:
173
- bond_hex = sc.window.settings.get('bond_color', '#222222')
174
- bond_color = QColor(bond_hex)
175
- painter.setPen(QPen(bond_color, 2))
188
+ # Allow bond color override from app settings (2D color)
189
+ width_2d = 2.0
190
+
191
+ try:
192
+ sc = self.scene()
193
+ if sc is not None and hasattr(sc, 'window') and sc.window is not None:
194
+ # Get settings
195
+ settings = sc.window.settings
196
+
197
+ # Width
198
+ width_2d = settings.get('bond_width_2d', 2.0)
199
+
200
+ # Cap Style logic
201
+ cap_style_str = settings.get('bond_cap_style_2d', 'Round')
202
+ cap_style = Qt.PenCapStyle.RoundCap # Default
203
+
204
+ if cap_style_str == 'Flat':
205
+ cap_style = Qt.PenCapStyle.FlatCap
206
+ elif cap_style_str == 'Square':
207
+ cap_style = Qt.PenCapStyle.SquareCap
208
+
209
+ # Color
210
+ if self.isSelected():
211
+ bond_color = QColor("blue") # Selection color
176
212
  else:
177
- painter.setPen(self.pen)
178
- except Exception:
213
+ bond_hex = settings.get('bond_color_2d', '#222222')
214
+ bond_color = QColor(bond_hex)
215
+
216
+ pen = QPen(bond_color, width_2d)
217
+ pen.setCapStyle(cap_style)
218
+ painter.setPen(pen)
219
+
220
+ else:
179
221
  painter.setPen(self.pen)
180
- painter.setBrush(QBrush(Qt.GlobalColor.black))
222
+ except Exception:
223
+ painter.setPen(self.pen)
224
+
225
+ painter.setBrush(QBrush(Qt.GlobalColor.black))
181
226
 
182
227
  # --- 立体化学 (Wedge/Dash) の描画 ---
183
228
  if self.order == 1 and self.stereo in [1, 2]:
@@ -213,7 +258,19 @@ class BondItem(QGraphicsItem):
213
258
  painter.drawLine(line)
214
259
  else:
215
260
  v = line.unitVector().normalVector()
216
- offset = QPointF(v.dx(), v.dy()) * BOND_OFFSET
261
+ # Use dynamic offset
262
+ bond_offset = 3.5
263
+ try:
264
+ sc = self.scene()
265
+ if sc and sc.views() and hasattr(sc.views()[0].window(), 'settings'):
266
+ if self.order == 3:
267
+ bond_offset = sc.views()[0].window().settings.get('bond_spacing_triple_2d', 3.5)
268
+ else:
269
+ bond_offset = sc.views()[0].window().settings.get('bond_spacing_double_2d', 3.5)
270
+ except Exception:
271
+ bond_offset = globals().get('BOND_OFFSET', 3.5)
272
+
273
+ offset = QPointF(v.dx(), v.dy()) * bond_offset
217
274
 
218
275
  if self.order == 2:
219
276
  # 環構造かどうかを判定し、描画方法を変更
@@ -274,7 +331,8 @@ class BondItem(QGraphicsItem):
274
331
  is_in_ring = False
275
332
 
276
333
  v = line.unitVector().normalVector()
277
- offset = QPointF(v.dx(), v.dy()) * BOND_OFFSET
334
+ # Re-calculate offset in case loop variable scope issue, though strictly not needed if offset defined above works
335
+ offset = QPointF(v.dx(), v.dy()) * bond_offset
278
336
 
279
337
  if is_in_ring and ring_center:
280
338
  # 環構造: 1本の中心線(単結合位置) + 1本の短い内側線
@@ -16,7 +16,7 @@ from PyQt6.QtGui import QFont, QColor
16
16
  from rdkit import Chem
17
17
 
18
18
  #Version
19
- VERSION = '2.3.2'
19
+ VERSION = '2.4.0'
20
20
 
21
21
  ATOM_RADIUS = 18
22
22
  BOND_OFFSET = 3.5
@@ -524,6 +524,10 @@ class MainWindow(QMainWindow):
524
524
  # --- MOVED TO main_window_edit_actions.py ---
525
525
  return self.main_window_edit_actions.adjust_molecule_positions_to_avoid_collisions(mol, frags)
526
526
 
527
+ def open_rotate_2d_dialog(self):
528
+ # --- MOVED TO main_window_edit_actions.py ---
529
+ return self.main_window_edit_actions.open_rotate_2d_dialog()
530
+
527
531
  def draw_molecule_3d(self, mol):
528
532
  # --- MOVED TO main_window_view_3d.py ---
529
533
  return self.main_window_view_3d.draw_molecule_3d(mol)
@@ -34,7 +34,7 @@ from rdkit.Chem import AllChem
34
34
 
35
35
  # PyQt6 Modules
36
36
  from PyQt6.QtWidgets import (
37
- QApplication
37
+ QApplication, QDialog, QVBoxLayout, QHBoxLayout, QLabel, QSpinBox, QSlider, QPushButton
38
38
  )
39
39
 
40
40
  from PyQt6.QtGui import (
@@ -43,9 +43,51 @@ from PyQt6.QtGui import (
43
43
 
44
44
 
45
45
  from PyQt6.QtCore import (
46
- QPointF, QLineF, QMimeData, QByteArray, QTimer
46
+ QPointF, QLineF, QMimeData, QByteArray, QTimer, Qt
47
47
  )
48
48
 
49
+ class Rotate2DDialog(QDialog):
50
+ def __init__(self, parent=None):
51
+ super().__init__(parent)
52
+ self.setWindowTitle("Rotate 2D")
53
+ self.setFixedWidth(300)
54
+
55
+ layout = QVBoxLayout(self)
56
+
57
+ # Angle input
58
+ input_layout = QHBoxLayout()
59
+ input_layout.addWidget(QLabel("Angle (degrees):"))
60
+ self.angle_spin = QSpinBox()
61
+ self.angle_spin.setRange(-360, 360)
62
+ self.angle_spin.setValue(45)
63
+ input_layout.addWidget(self.angle_spin)
64
+ layout.addLayout(input_layout)
65
+
66
+ # Slider
67
+ self.slider = QSlider(Qt.Orientation.Horizontal)
68
+ self.slider.setRange(-180, 180)
69
+ self.slider.setValue(45)
70
+ self.slider.setTickPosition(QSlider.TickPosition.TicksBelow)
71
+ self.slider.setTickInterval(15)
72
+ layout.addWidget(self.slider)
73
+
74
+ # Sync slider and spinbox
75
+ self.angle_spin.valueChanged.connect(self.slider.setValue)
76
+ self.slider.valueChanged.connect(self.angle_spin.setValue)
77
+
78
+ # Buttons
79
+ btn_layout = QHBoxLayout()
80
+ ok_btn = QPushButton("Rotate")
81
+ ok_btn.clicked.connect(self.accept)
82
+ cancel_btn = QPushButton("Cancel")
83
+ cancel_btn.clicked.connect(self.reject)
84
+ btn_layout.addWidget(ok_btn)
85
+ btn_layout.addWidget(cancel_btn)
86
+ layout.addLayout(btn_layout)
87
+
88
+ def get_angle(self):
89
+ return self.angle_spin.value()
90
+
49
91
 
50
92
  # Use centralized Open Babel availability from package-level __init__
51
93
  # Use per-package modules availability (local __init__).
@@ -568,6 +610,67 @@ class MainWindowEditActions(object):
568
610
 
569
611
 
570
612
 
613
+ def open_rotate_2d_dialog(self):
614
+ """2D回転ダイアログを開く"""
615
+ dialog = Rotate2DDialog(self)
616
+ if dialog.exec() == QDialog.DialogCode.Accepted:
617
+ angle = dialog.get_angle()
618
+ self.rotate_molecule_2d(angle)
619
+
620
+ def rotate_molecule_2d(self, angle_degrees):
621
+ """2D分子を指定角度回転させる(選択範囲があればそれのみ、なければ全体)"""
622
+ try:
623
+ # Determine target atoms
624
+ selected_items = self.scene.selectedItems()
625
+ target_atoms = [item for item in selected_items if isinstance(item, AtomItem)]
626
+
627
+ # If no selection, rotate everything
628
+ if not target_atoms:
629
+ target_atoms = [data['item'] for data in self.data.atoms.values() if data.get('item') and not sip_isdeleted_safe(data['item'])]
630
+
631
+ if not target_atoms:
632
+ self.statusBar().showMessage("No atoms to rotate.")
633
+ return
634
+
635
+ # Calculate Center
636
+ xs = [atom.pos().x() for atom in target_atoms]
637
+ ys = [atom.pos().y() for atom in target_atoms]
638
+ if not xs: return
639
+
640
+ center_x = sum(xs) / len(xs)
641
+ center_y = sum(ys) / len(ys)
642
+ center = QPointF(center_x, center_y)
643
+
644
+ rad = math.radians(angle_degrees)
645
+ cos_a = math.cos(rad)
646
+ sin_a = math.sin(rad)
647
+
648
+ for atom in target_atoms:
649
+ # Relative pos
650
+ dx = atom.pos().x() - center_x
651
+ dy = atom.pos().y() - center_y
652
+
653
+ # Rotate
654
+ new_dx = dx * cos_a - dy * sin_a
655
+ new_dy = dx * sin_a + dy * cos_a
656
+
657
+ new_pos = QPointF(center_x + new_dx, center_y + new_dy)
658
+ atom.setPos(new_pos)
659
+
660
+ # Update bonds
661
+ self.scene.update_connected_bonds(target_atoms)
662
+
663
+ self.push_undo_state()
664
+ self.statusBar().showMessage(f"Rotated {len(target_atoms)} atoms by {angle_degrees} degrees.")
665
+ self.scene.update()
666
+
667
+ except Exception as e:
668
+ print(f"Error rotating molecule: {e}")
669
+ traceback.print_exc()
670
+ self.statusBar().showMessage(f"Error rotating: {e}")
671
+
672
+
673
+
571
674
 
572
675
  def select_all(self):
573
676
  for item in self.scene.items():
@@ -641,6 +744,10 @@ class MainWindowEditActions(object):
641
744
  # アプリケーションのイベントループを強制的に処理し、画面の再描画を確実に行う
642
745
  QApplication.processEvents()
643
746
 
747
+ # Call plugin document reset handlers
748
+ if hasattr(self, 'plugin_manager') and self.plugin_manager:
749
+ self.plugin_manager.invoke_document_reset_handlers()
750
+
644
751
  self.statusBar().showMessage("Cleared all data.")
645
752
 
646
753
 
@@ -672,7 +672,7 @@ class MainWindowExport(object):
672
672
  filePath += ".png"
673
673
 
674
674
  reply = QMessageBox.question(self, 'Choose Background',
675
- 'Do you want a transparent background?\n(Choose "No" for a white background)',
675
+ 'Do you want a transparent background?\n(Choose "No" to use the current background color)',
676
676
  QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No | QMessageBox.StandardButton.Cancel,
677
677
  QMessageBox.StandardButton.Yes)
678
678
 
@@ -706,8 +706,7 @@ class MainWindowExport(object):
706
706
 
707
707
  if is_transparent:
708
708
  self.scene.setBackgroundBrush(QBrush(Qt.BrushStyle.NoBrush))
709
- else:
710
- self.scene.setBackgroundBrush(QBrush(QColor("#FFFFFF")))
709
+ # Else: keep original_background (current 2D background)
711
710
 
712
711
  rect_to_render = molecule_bounds.adjusted(-20, -20, 20, 20)
713
712
 
@@ -719,10 +718,8 @@ class MainWindowExport(object):
719
718
  return
720
719
 
721
720
  image = QImage(w, h, QImage.Format.Format_ARGB32_Premultiplied)
722
- if is_transparent:
723
- image.fill(Qt.GlobalColor.transparent)
724
- else:
725
- image.fill(Qt.GlobalColor.white)
721
+ # Always fill with transparent; render will paint opaque background if present
722
+ image.fill(Qt.GlobalColor.transparent)
726
723
 
727
724
  painter = QPainter()
728
725
  ok = painter.begin(image)
@@ -372,7 +372,7 @@ class MainWindowMainInit(object):
372
372
 
373
373
  # --- 左パネルのボタンレイアウト ---
374
374
  left_buttons_layout = QHBoxLayout()
375
- self.cleanup_button = QPushButton("Optimize 2D")
375
+ self.cleanup_button = QPushButton("Clean Up 2D")
376
376
  self.cleanup_button.clicked.connect(self.clean_up_2d_structure)
377
377
  left_buttons_layout.addWidget(self.cleanup_button)
378
378
 
@@ -987,7 +987,13 @@ class MainWindowMainInit(object):
987
987
 
988
988
  edit_menu.addSeparator()
989
989
 
990
- optimize_2d_action = QAction("Optimize 2D", self)
990
+ rotate_2d_action = QAction("Rotate 2D...", self)
991
+ rotate_2d_action.triggered.connect(self.open_rotate_2d_dialog)
992
+ edit_menu.addAction(rotate_2d_action)
993
+
994
+ edit_menu.addSeparator()
995
+
996
+ optimize_2d_action = QAction("Clean Up 2D", self)
991
997
  optimize_2d_action.setShortcut(QKeySequence("Ctrl+J"))
992
998
  optimize_2d_action.triggered.connect(self.clean_up_2d_structure)
993
999
  edit_menu.addAction(optimize_2d_action)
@@ -1247,7 +1253,7 @@ class MainWindowMainInit(object):
1247
1253
 
1248
1254
  settings_menu = menu_bar.addMenu("&Settings")
1249
1255
  # 1) 3D View settings (existing)
1250
- view_settings_action = QAction("3D View Settings...", self)
1256
+ view_settings_action = QAction("Settings...", self)
1251
1257
  view_settings_action.triggered.connect(self.open_settings_dialog)
1252
1258
  settings_menu.addAction(view_settings_action)
1253
1259
 
@@ -1495,6 +1501,10 @@ class MainWindowMainInit(object):
1495
1501
 
1496
1502
  try:
1497
1503
  if hasattr(self, 'scene') and self.scene:
1504
+ # Apply 2D background color
1505
+ bg_color_2d = self.settings.get('background_color_2d', '#FFFFFF')
1506
+ self.scene.setBackgroundBrush(QBrush(QColor(bg_color_2d)))
1507
+
1498
1508
  for it in list(self.scene.items()):
1499
1509
  if hasattr(it, 'update_style'):
1500
1510
  it.update_style()
@@ -1674,6 +1684,18 @@ class MainWindowMainInit(object):
1674
1684
  # Whether to kekulize aromatic systems for 3D display
1675
1685
  'display_kekule_3d': False,
1676
1686
  'always_ask_charge': False,
1687
+ 'display_aromatic_circles_3d': False,
1688
+ 'ball_stick_use_cpk_bond_color': False,
1689
+
1690
+ # --- 2D Settings Defaults ---
1691
+ 'bond_width_2d': 2.0,
1692
+ 'bond_spacing_double_2d': 3.5,
1693
+ 'bond_spacing_triple_2d': 3.5,
1694
+ 'atom_font_size_2d': 20,
1695
+ 'background_color_2d': '#FFFFFF',
1696
+ 'bond_color_2d': '#222222', # Almost black
1697
+ 'atom_use_bond_color_2d': False,
1698
+ 'bond_cap_style_2d': 'Round',
1677
1699
  }
1678
1700
 
1679
1701
  try:
@@ -1851,6 +1873,11 @@ class MainWindowMainInit(object):
1851
1873
  action_text = text if text else parts[-1]
1852
1874
  action = QAction(action_text, self)
1853
1875
  action.triggered.connect(callback)
1876
+
1877
+ # Apply shortcut if provided
1878
+ if action_def.get('shortcut'):
1879
+ action.setShortcut(QKeySequence(action_def['shortcut']))
1880
+
1854
1881
  action.setData(PLUGIN_ACTION_TAG) # TAG THE ACTION
1855
1882
  current_menu.addAction(action)
1856
1883
 
@@ -168,6 +168,16 @@ class PluginContext:
168
168
  """
169
169
  self._manager.register_3d_style(self._plugin_name, style_name, callback)
170
170
 
171
+ def register_document_reset_handler(self, callback: Callable[[], None]):
172
+ """
173
+ Register a callback to be called when a new document is created (File→New).
174
+
175
+ Args:
176
+ callback: Function with no arguments that resets plugin state.
177
+ """
178
+ self._manager.register_document_reset_handler(self._plugin_name, callback)
179
+
180
+
171
181
 
172
182
 
173
183
 
@@ -51,6 +51,7 @@ class PluginManager:
51
51
  self.save_handlers = {}
52
52
  self.load_handlers = {}
53
53
  self.custom_3d_styles = {} # style_name -> {'plugin': name, 'callback': func}
54
+ self.document_reset_handlers = [] # List of callbacks to call on new document
54
55
 
55
56
  def get_main_window(self):
56
57
  return self.main_window
@@ -182,6 +183,7 @@ class PluginManager:
182
183
  self.save_handlers = {}
183
184
  self.load_handlers = {}
184
185
  self.custom_3d_styles = {}
186
+ self.document_reset_handlers = []
185
187
 
186
188
  if not os.path.exists(self.plugin_dir):
187
189
  return []
@@ -378,6 +380,21 @@ class PluginManager:
378
380
  self.custom_3d_styles[style_name] = {
379
381
  'plugin': plugin_name, 'callback': callback
380
382
  }
383
+
384
+ def register_document_reset_handler(self, plugin_name, callback):
385
+ """Register callback to be invoked when a new document is created."""
386
+ self.document_reset_handlers.append({
387
+ 'plugin': plugin_name,
388
+ 'callback': callback
389
+ })
390
+
391
+ def invoke_document_reset_handlers(self):
392
+ """Call all registered document reset handlers."""
393
+ for handler in self.document_reset_handlers:
394
+ try:
395
+ handler['callback']()
396
+ except Exception as e:
397
+ print(f"Error in document reset handler for {handler['plugin']}: {e}")
381
398
 
382
399
  def get_plugin_info_safe(self, file_path):
383
400
  """Extracts plugin metadata using AST parsing (safe, no execution)."""
@@ -26,8 +26,8 @@ except Exception:
26
26
  class SettingsDialog(QDialog):
27
27
  def __init__(self, current_settings, parent=None):
28
28
  super().__init__(parent)
29
- self.setWindowTitle("3D View Settings")
30
- self.setMinimumSize(500, 600)
29
+ self.setWindowTitle("Settings")
30
+ self.setMinimumSize(700, 800)
31
31
 
32
32
  # 親ウィンドウの参照を保存(Apply機能のため)
33
33
  self.parent_window = parent
@@ -89,6 +89,16 @@ class SettingsDialog(QDialog):
89
89
  # (shows alternating single/double bonds rather than aromatic circles)
90
90
  'display_kekule_3d': False,
91
91
  'ball_stick_use_cpk_bond_color': False,
92
+
93
+ # --- 2D Settings Defaults ---
94
+ 'bond_width_2d': 2.0,
95
+ 'bond_spacing_double_2d': 3.5,
96
+ 'bond_spacing_triple_2d': 3.5,
97
+ 'atom_font_size_2d': 20,
98
+ 'background_color_2d': '#FFFFFF',
99
+ 'bond_color_2d': '#222222', # Almost black
100
+ 'atom_use_bond_color_2d': False,
101
+ 'bond_cap_style_2d': 'Round',
92
102
  }
93
103
 
94
104
  # --- 選択された色を管理する専用のインスタンス変数 ---
@@ -101,6 +111,9 @@ class SettingsDialog(QDialog):
101
111
  self.tab_widget = QTabWidget()
102
112
  layout.addWidget(self.tab_widget)
103
113
 
114
+ # 2D Settings Tab (First)
115
+ self.create_2d_settings_tab()
116
+
104
117
  # Scene設定タブ
105
118
  self.create_scene_tab()
106
119
 
@@ -129,6 +142,13 @@ class SettingsDialog(QDialog):
129
142
  self.aromatic_torus_thickness_slider.setValue(int(thickness_factor * 100))
130
143
  self.aromatic_torus_thickness_label.setText(f"{thickness_factor:.1f}")
131
144
 
145
+ # Initialize internal 2D colors
146
+ self.current_bg_color_2d = current_settings.get('background_color_2d', self.default_settings['background_color_2d'])
147
+ self.current_bond_color_2d = current_settings.get('bond_color_2d', self.default_settings['bond_color_2d'])
148
+
149
+ # Apply initial 2D button styles
150
+ self.update_2d_color_buttons()
151
+
132
152
  # --- ボタンの配置 ---
133
153
  buttons = QHBoxLayout()
134
154
 
@@ -160,6 +180,121 @@ class SettingsDialog(QDialog):
160
180
  buttons.addWidget(ok_button)
161
181
  buttons.addWidget(cancel_button)
162
182
  layout.addLayout(buttons)
183
+
184
+ def create_2d_settings_tab(self):
185
+ """2D Settings Tab"""
186
+ widget = QWidget()
187
+ form_layout = QFormLayout(widget)
188
+
189
+ # --- View Settings ---
190
+ form_layout.addRow(QLabel("<b>View Appearance</b>"))
191
+
192
+ # Background Color
193
+ self.bg_color_2d_button = QPushButton()
194
+ self.bg_color_2d_button.setFixedSize(60, 24)
195
+ self.bg_color_2d_button.clicked.connect(self.pick_bg_color_2d)
196
+ form_layout.addRow("Background Color:", self.bg_color_2d_button)
197
+
198
+ line = QFrame()
199
+ line.setFrameShape(QFrame.Shape.HLine)
200
+ line.setFrameShadow(QFrame.Shadow.Sunken)
201
+ form_layout.addRow(line)
202
+
203
+ # --- Bond Settings ---
204
+ form_layout.addRow(QLabel("<b>Bond Settings</b>"))
205
+
206
+ # Bond Color
207
+ self.bond_color_2d_button = QPushButton()
208
+ self.bond_color_2d_button.setFixedSize(60, 24)
209
+ self.bond_color_2d_button.clicked.connect(self.pick_bond_color_2d)
210
+ form_layout.addRow("Bond Color:", self.bond_color_2d_button)
211
+
212
+ # Bond Width
213
+ self.bond_width_2d_slider = QSlider(Qt.Orientation.Horizontal)
214
+ self.bond_width_2d_slider.setRange(10, 200) # 1.0 - 20.0
215
+ self.bond_width_2d_label = QLabel("2.0")
216
+ self.bond_width_2d_slider.valueChanged.connect(lambda v: self.bond_width_2d_label.setText(f"{v/10:.1f}"))
217
+ bw_layout = QHBoxLayout()
218
+ bw_layout.addWidget(self.bond_width_2d_slider)
219
+ bw_layout.addWidget(self.bond_width_2d_label)
220
+ form_layout.addRow("Bond Width:", bw_layout)
221
+
222
+ # Double Bond Spacing
223
+ self.bond_spacing_double_2d_slider = QSlider(Qt.Orientation.Horizontal)
224
+ self.bond_spacing_double_2d_slider.setRange(10, 200) # 1.0 - 20.0
225
+ self.bond_spacing_double_2d_label = QLabel("3.5")
226
+ self.bond_spacing_double_2d_slider.valueChanged.connect(lambda v: self.bond_spacing_double_2d_label.setText(f"{v/10:.1f}"))
227
+ bsd_layout = QHBoxLayout()
228
+ bsd_layout.addWidget(self.bond_spacing_double_2d_slider)
229
+ bsd_layout.addWidget(self.bond_spacing_double_2d_label)
230
+ form_layout.addRow("Double Bond Spacing:", bsd_layout)
231
+
232
+ # Triple Bond Spacing
233
+ self.bond_spacing_triple_2d_slider = QSlider(Qt.Orientation.Horizontal)
234
+ self.bond_spacing_triple_2d_slider.setRange(10, 200) # 1.0 - 20.0
235
+ self.bond_spacing_triple_2d_label = QLabel("3.5")
236
+ self.bond_spacing_triple_2d_slider.valueChanged.connect(lambda v: self.bond_spacing_triple_2d_label.setText(f"{v/10:.1f}"))
237
+ bst_layout = QHBoxLayout()
238
+ bst_layout.addWidget(self.bond_spacing_triple_2d_slider)
239
+ bst_layout.addWidget(self.bond_spacing_triple_2d_label)
240
+ form_layout.addRow("Triple Bond Spacing:", bst_layout)
241
+
242
+ # Bond Cap Style
243
+ self.bond_cap_style_2d_combo = QComboBox()
244
+ self.bond_cap_style_2d_combo.addItems(['Round', 'Flat', 'Square'])
245
+ form_layout.addRow("Bond Cap Style:", self.bond_cap_style_2d_combo)
246
+
247
+
248
+ line2 = QFrame()
249
+ line2.setFrameShape(QFrame.Shape.HLine)
250
+ line2.setFrameShadow(QFrame.Shadow.Sunken)
251
+ form_layout.addRow(line2)
252
+
253
+ # --- Atom Settings ---
254
+ form_layout.addRow(QLabel("<b>Atom Settings</b>"))
255
+
256
+ # Font Size
257
+ self.atom_font_size_2d_slider = QSlider(Qt.Orientation.Horizontal)
258
+ self.atom_font_size_2d_slider.setRange(8, 72)
259
+ self.atom_font_size_2d_label = QLabel("20")
260
+ self.atom_font_size_2d_slider.valueChanged.connect(lambda v: self.atom_font_size_2d_label.setText(str(v)))
261
+ fs_layout = QHBoxLayout()
262
+ fs_layout.addWidget(self.atom_font_size_2d_slider)
263
+ fs_layout.addWidget(self.atom_font_size_2d_label)
264
+ form_layout.addRow("Atom Label Font Size:", fs_layout)
265
+
266
+ # Use Bond Color Checkbox
267
+ self.atom_use_bond_color_2d_checkbox = QCheckBox()
268
+ self.atom_use_bond_color_2d_checkbox.setToolTip("If checked, atoms will use the unified Bond Color instead of element-specific colors (CPK).")
269
+ form_layout.addRow("Use Bond Color for Atoms:", self.atom_use_bond_color_2d_checkbox)
270
+
271
+ self.tab_widget.addTab(widget, "2D Settings")
272
+
273
+
274
+
275
+ # Initialize internal variable for combo updates
276
+ # (Usually done in update_ui_from_settings, but we add direct access here for simple binding if needed)
277
+ # We rely on update_ui_from_settings to set current selection.
278
+
279
+
280
+ def pick_bg_color_2d(self):
281
+ color = QColorDialog.getColor(QColor(self.current_bg_color_2d), self, "Select 2D Background Color")
282
+ if color.isValid():
283
+ self.current_bg_color_2d = color.name()
284
+ self.update_2d_color_buttons()
285
+
286
+ def pick_bond_color_2d(self):
287
+ color = QColorDialog.getColor(QColor(self.current_bond_color_2d), self, "Select 2D Bond Color")
288
+ if color.isValid():
289
+ self.current_bond_color_2d = color.name()
290
+ self.update_2d_color_buttons()
291
+
292
+ def update_2d_color_buttons(self):
293
+ try:
294
+ self.bg_color_2d_button.setStyleSheet(f"background-color: {self.current_bg_color_2d}; border: 1px solid #888;")
295
+ self.bond_color_2d_button.setStyleSheet(f"background-color: {self.current_bond_color_2d}; border: 1px solid #888;")
296
+ except Exception:
297
+ pass
163
298
 
164
299
  def create_scene_tab(self):
165
300
  """基本設定タブを作成"""
@@ -217,7 +352,7 @@ class SettingsDialog(QDialog):
217
352
  self.projection_combo.setToolTip("Choose camera projection mode: Perspective (default) or Orthographic")
218
353
  form_layout.addRow("Projection Mode:", self.projection_combo)
219
354
 
220
- self.tab_widget.addTab(scene_widget, "Scene")
355
+ self.tab_widget.addTab(scene_widget, "3D Scene")
221
356
 
222
357
  def create_other_tab(self):
223
358
  """other設定タブを作成"""
@@ -643,11 +778,22 @@ class SettingsDialog(QDialog):
643
778
  tab_name = self.tab_widget.tabText(current_tab_index)
644
779
 
645
780
  # 各タブの設定項目を定義
646
- # Note: tab labels must match those added to the QTabWidget ("Scene", "Ball & Stick",
781
+ # Each tab settings
782
+ # Note: tab labels must match those added to the QTabWidget ("2D Settings", "Scene", "Ball & Stick", ...
647
783
  # "CPK (Space-filling)", "Wireframe", "Stick", "Other"). Use the per-model
648
784
  # multi-bond keys present in self.default_settings.
649
785
  tab_settings = {
650
- "Scene": {
786
+ "2D Settings": {
787
+ 'bond_width_2d': self.default_settings['bond_width_2d'],
788
+ 'bond_spacing_double_2d': self.default_settings['bond_spacing_double_2d'],
789
+ 'bond_spacing_triple_2d': self.default_settings['bond_spacing_triple_2d'],
790
+ 'atom_font_size_2d': self.default_settings['atom_font_size_2d'],
791
+ 'background_color_2d': self.default_settings['background_color_2d'],
792
+ 'bond_color_2d': self.default_settings['bond_color_2d'],
793
+ 'atom_use_bond_color_2d': self.default_settings['atom_use_bond_color_2d'],
794
+ 'bond_cap_style_2d': self.default_settings['bond_cap_style_2d']
795
+ },
796
+ "3D Scene": {
651
797
  'background_color': self.default_settings['background_color'],
652
798
  'projection_mode': self.default_settings['projection_mode'],
653
799
  'show_3d_axes': self.default_settings['show_3d_axes'],
@@ -711,6 +857,13 @@ class SettingsDialog(QDialog):
711
857
 
712
858
  # UIを更新
713
859
  self.update_ui_from_settings(updated_settings)
860
+
861
+ # If 2D settings were reset, update internal color variables too
862
+ if tab_name == "2D Settings":
863
+ self.current_bg_color_2d = updated_settings.get('background_color_2d', self.default_settings['background_color_2d'])
864
+ self.current_bond_color_2d = updated_settings.get('bond_color_2d', self.default_settings['bond_color_2d'])
865
+ self.update_2d_color_buttons()
866
+
714
867
  # CPK tab: do not change parent/settings immediately; let Apply/OK persist any changes
715
868
 
716
869
  # ユーザーへのフィードバック
@@ -748,7 +901,9 @@ class SettingsDialog(QDialog):
748
901
  if 'cpk_colors' in self.parent_window.settings:
749
902
  # Reset to defaults (empty override dict)
750
903
  self.parent_window.settings['cpk_colors'] = {}
751
- # 2D bond color is fixed and not part of the settings; do not modify it here
904
+
905
+ # Reset 2D Settings (colors need manual refresh in parent scene/view usually handled by apply)
906
+
752
907
  # Reset 3D Ball & Stick uniform bond color to default
753
908
  self.parent_window.settings['ball_stick_bond_color'] = self.default_settings.get('ball_stick_bond_color', '#7F7F7F')
754
909
  # Update global CPK colors and reapply 3D settings immediately
@@ -1063,9 +1218,17 @@ class SettingsDialog(QDialog):
1063
1218
  'aromatic_torus_thickness_factor': self.aromatic_torus_thickness_slider.value() / 100.0,
1064
1219
  'skip_chemistry_checks': self.skip_chem_checks_checkbox.isChecked(),
1065
1220
  'always_ask_charge': self.always_ask_charge_checkbox.isChecked(),
1066
- # Ball & Stick bond color (3D grey/uniform color)
1067
1221
  'ball_stick_bond_color': getattr(self, 'bs_bond_color', self.default_settings.get('ball_stick_bond_color', '#7F7F7F')),
1068
1222
  'ball_stick_use_cpk_bond_color': self.bs_use_cpk_bond_checkbox.isChecked(),
1223
+ # 2D Settings
1224
+ 'bond_width_2d': self.bond_width_2d_slider.value() / 10.0,
1225
+ 'bond_spacing_double_2d': self.bond_spacing_double_2d_slider.value() / 10.0,
1226
+ 'bond_spacing_triple_2d': self.bond_spacing_triple_2d_slider.value() / 10.0,
1227
+ 'atom_font_size_2d': self.atom_font_size_2d_slider.value(),
1228
+ 'background_color_2d': self.current_bg_color_2d,
1229
+ 'bond_color_2d': self.current_bond_color_2d,
1230
+ 'atom_use_bond_color_2d': self.atom_use_bond_color_2d_checkbox.isChecked(),
1231
+ 'bond_cap_style_2d': self.bond_cap_style_2d_combo.currentText(),
1069
1232
  }
1070
1233
 
1071
1234
  def pick_bs_bond_color(self):
@@ -1119,6 +1282,27 @@ class SettingsDialog(QDialog):
1119
1282
  # 現在の分子を再描画(設定変更を反映)
1120
1283
  if hasattr(self.parent_window, 'current_mol') and self.parent_window.current_mol:
1121
1284
  self.parent_window.draw_molecule_3d(self.parent_window.current_mol)
1285
+
1286
+ # 2Dビューの設定適用 (背景色、結合スタイルなど)
1287
+ # update_style() will read the new settings from parent_window.settings
1288
+ try:
1289
+ if hasattr(self.parent_window, 'scene') and self.parent_window.scene:
1290
+ # Update Background
1291
+ bg_col_2d = self.parent_window.settings.get('background_color_2d', '#FFFFFF')
1292
+ self.parent_window.scene.setBackgroundBrush(QColor(bg_col_2d))
1293
+
1294
+ # Update Items
1295
+ for item in self.parent_window.scene.items():
1296
+ if hasattr(item, 'update_style'):
1297
+ item.update_style()
1298
+ elif hasattr(item, 'update'):
1299
+ item.update()
1300
+
1301
+ if hasattr(self.parent_window.view_2d, 'viewport'):
1302
+ self.parent_window.view_2d.viewport().update()
1303
+ except Exception:
1304
+ pass
1305
+
1122
1306
  # ステータスバーに適用完了を表示
1123
1307
  self.parent_window.statusBar().showMessage("Settings applied successfully")
1124
1308
 
@@ -1147,3 +1331,173 @@ class SettingsDialog(QDialog):
1147
1331
  # apply_settingsを呼び出して設定を適用
1148
1332
  self.apply_settings()
1149
1333
  super().accept()
1334
+
1335
+ def update_ui_from_settings(self, settings_dict):
1336
+ """設定辞書に基づいてUIを更新"""
1337
+ # 1. Scene settings
1338
+ self.current_bg_color = settings_dict.get('background_color', self.default_settings['background_color'])
1339
+ self.update_color_button(self.current_bg_color)
1340
+
1341
+ self.axes_checkbox.setChecked(settings_dict.get('show_3d_axes', self.default_settings['show_3d_axes']))
1342
+ self.light_checkbox.setChecked(settings_dict.get('lighting_enabled', self.default_settings['lighting_enabled']))
1343
+
1344
+ intensity = settings_dict.get('light_intensity', self.default_settings['light_intensity'])
1345
+ self.intensity_slider.setValue(int(intensity * 100))
1346
+ self.intensity_label.setText(f"{intensity:.2f}")
1347
+
1348
+ specular = settings_dict.get('specular', self.default_settings['specular'])
1349
+ self.specular_slider.setValue(int(specular * 100))
1350
+ self.specular_label.setText(f"{specular:.2f}")
1351
+
1352
+ spec_power = settings_dict.get('specular_power', self.default_settings['specular_power'])
1353
+ self.spec_power_slider.setValue(int(spec_power))
1354
+ self.spec_power_label.setText(str(spec_power))
1355
+
1356
+ proj_mode = settings_dict.get('projection_mode', self.default_settings['projection_mode'])
1357
+ idx = self.projection_combo.findText(proj_mode)
1358
+ if idx >= 0:
1359
+ self.projection_combo.setCurrentIndex(idx)
1360
+
1361
+ # 2. Ball & Stick settings
1362
+ bs_atom_scale = settings_dict.get('ball_stick_atom_scale', self.default_settings['ball_stick_atom_scale'])
1363
+ self.bs_atom_scale_slider.setValue(int(bs_atom_scale * 100))
1364
+ self.bs_atom_scale_label.setText(f"{bs_atom_scale:.2f}")
1365
+
1366
+ bs_bond_radius = settings_dict.get('ball_stick_bond_radius', self.default_settings['ball_stick_bond_radius'])
1367
+ self.bs_bond_radius_slider.setValue(int(bs_bond_radius * 100))
1368
+ self.bs_bond_radius_label.setText(f"{bs_bond_radius:.2f}")
1369
+
1370
+ # Multi-bond offsets (Ball & Stick)
1371
+ bs_db_offset = settings_dict.get('ball_stick_double_bond_offset_factor', self.default_settings.get('ball_stick_double_bond_offset_factor', 2.0))
1372
+ self.bs_double_offset_slider.setValue(int(bs_db_offset * 100))
1373
+ self.bs_double_offset_label.setText(f"{bs_db_offset:.2f}")
1374
+
1375
+ bs_tr_offset = settings_dict.get('ball_stick_triple_bond_offset_factor', self.default_settings.get('ball_stick_triple_bond_offset_factor', 2.0))
1376
+ self.bs_triple_offset_slider.setValue(int(bs_tr_offset * 100))
1377
+ self.bs_triple_offset_label.setText(f"{bs_tr_offset:.2f}")
1378
+
1379
+ bs_db_rad = settings_dict.get('ball_stick_double_bond_radius_factor', self.default_settings.get('ball_stick_double_bond_radius_factor', 0.8))
1380
+ self.bs_double_radius_slider.setValue(int(bs_db_rad * 100))
1381
+ self.bs_double_radius_label.setText(f"{bs_db_rad:.2f}")
1382
+
1383
+ bs_tr_rad = settings_dict.get('ball_stick_triple_bond_radius_factor', self.default_settings.get('ball_stick_triple_bond_radius_factor', 0.75))
1384
+ self.bs_triple_radius_slider.setValue(int(bs_tr_rad * 100))
1385
+ self.bs_triple_radius_label.setText(f"{bs_tr_rad:.2f}")
1386
+
1387
+ bs_res = settings_dict.get('ball_stick_resolution', self.default_settings['ball_stick_resolution'])
1388
+ self.bs_resolution_slider.setValue(int(bs_res))
1389
+ self.bs_resolution_label.setText(str(bs_res))
1390
+
1391
+ # Ball & Stick bond color
1392
+ self.bs_bond_color = settings_dict.get('ball_stick_bond_color', self.default_settings.get('ball_stick_bond_color', '#7F7F7F'))
1393
+ try:
1394
+ self.bs_bond_color_button.setStyleSheet(f"background-color: {self.bs_bond_color}; border: 1px solid #888;")
1395
+ self.bs_bond_color_button.setToolTip(self.bs_bond_color)
1396
+ except Exception:
1397
+ pass
1398
+
1399
+ self.bs_use_cpk_bond_checkbox.setChecked(settings_dict.get('ball_stick_use_cpk_bond_color', self.default_settings.get('ball_stick_use_cpk_bond_color', False)))
1400
+
1401
+ # 3. CPK settings
1402
+ cpk_atom_scale = settings_dict.get('cpk_atom_scale', self.default_settings['cpk_atom_scale'])
1403
+ self.cpk_atom_scale_slider.setValue(int(cpk_atom_scale * 100))
1404
+ self.cpk_atom_scale_label.setText(f"{cpk_atom_scale:.2f}")
1405
+
1406
+ cpk_res = settings_dict.get('cpk_resolution', self.default_settings['cpk_resolution'])
1407
+ self.cpk_resolution_slider.setValue(int(cpk_res))
1408
+ self.cpk_resolution_label.setText(str(cpk_res))
1409
+
1410
+ # 4. Wireframe settings
1411
+ wf_bond_radius = settings_dict.get('wireframe_bond_radius', self.default_settings['wireframe_bond_radius'])
1412
+ self.wf_bond_radius_slider.setValue(int(wf_bond_radius * 100))
1413
+ self.wf_bond_radius_label.setText(f"{wf_bond_radius:.2f}")
1414
+
1415
+ wf_res = settings_dict.get('wireframe_resolution', self.default_settings['wireframe_resolution'])
1416
+ self.wf_resolution_slider.setValue(int(wf_res))
1417
+ self.wf_resolution_label.setText(str(wf_res))
1418
+
1419
+ # Multi-bond offsets (Wireframe)
1420
+ wf_db_offset = settings_dict.get('wireframe_double_bond_offset_factor', self.default_settings.get('wireframe_double_bond_offset_factor', 3.0))
1421
+ self.wf_double_offset_slider.setValue(int(wf_db_offset * 100))
1422
+ self.wf_double_offset_label.setText(f"{wf_db_offset:.2f}")
1423
+
1424
+ wf_tr_offset = settings_dict.get('wireframe_triple_bond_offset_factor', self.default_settings.get('wireframe_triple_bond_offset_factor', 3.0))
1425
+ self.wf_triple_offset_slider.setValue(int(wf_tr_offset * 100))
1426
+ self.wf_triple_offset_label.setText(f"{wf_tr_offset:.2f}")
1427
+
1428
+ wf_db_rad = settings_dict.get('wireframe_double_bond_radius_factor', self.default_settings.get('wireframe_double_bond_radius_factor', 0.8))
1429
+ self.wf_double_radius_slider.setValue(int(wf_db_rad * 100))
1430
+ self.wf_double_radius_label.setText(f"{wf_db_rad:.2f}")
1431
+
1432
+ wf_tr_rad = settings_dict.get('wireframe_triple_bond_radius_factor', self.default_settings.get('wireframe_triple_bond_radius_factor', 0.75))
1433
+ self.wf_triple_radius_slider.setValue(int(wf_tr_rad * 100))
1434
+ self.wf_triple_radius_label.setText(f"{wf_tr_rad:.2f}")
1435
+
1436
+ # 5. Stick settings
1437
+ stick_bond_radius = settings_dict.get('stick_bond_radius', self.default_settings['stick_bond_radius'])
1438
+ self.stick_bond_radius_slider.setValue(int(stick_bond_radius * 100))
1439
+ self.stick_bond_radius_label.setText(f"{stick_bond_radius:.2f}")
1440
+
1441
+ stick_res = settings_dict.get('stick_resolution', self.default_settings['stick_resolution'])
1442
+ self.stick_resolution_slider.setValue(int(stick_res))
1443
+ self.stick_resolution_label.setText(str(stick_res))
1444
+
1445
+ # Multi-bond offsets (Stick)
1446
+ stick_db_offset = settings_dict.get('stick_double_bond_offset_factor', self.default_settings.get('stick_double_bond_offset_factor', 1.5))
1447
+ self.stick_double_offset_slider.setValue(int(stick_db_offset * 100))
1448
+ self.stick_double_offset_label.setText(f"{stick_db_offset:.2f}")
1449
+
1450
+ stick_tr_offset = settings_dict.get('stick_triple_bond_offset_factor', self.default_settings.get('stick_triple_bond_offset_factor', 1.0))
1451
+ self.stick_triple_offset_slider.setValue(int(stick_tr_offset * 100))
1452
+ self.stick_triple_offset_label.setText(f"{stick_tr_offset:.2f}")
1453
+
1454
+ stick_db_rad = settings_dict.get('stick_double_bond_radius_factor', self.default_settings.get('stick_double_bond_radius_factor', 0.6))
1455
+ self.stick_double_radius_slider.setValue(int(stick_db_rad * 100))
1456
+ self.stick_double_radius_label.setText(f"{stick_db_rad:.2f}")
1457
+
1458
+ stick_tr_rad = settings_dict.get('stick_triple_bond_radius_factor', self.default_settings.get('stick_triple_bond_radius_factor', 0.4))
1459
+ self.stick_triple_radius_slider.setValue(int(stick_tr_rad * 100))
1460
+ self.stick_triple_radius_label.setText(f"{stick_tr_rad:.2f}")
1461
+
1462
+ # 6. Other settings
1463
+ self.skip_chem_checks_checkbox.setChecked(settings_dict.get('skip_chemistry_checks', self.default_settings.get('skip_chemistry_checks', False)))
1464
+ self.kekule_3d_checkbox.setChecked(settings_dict.get('display_kekule_3d', self.default_settings.get('display_kekule_3d', False)))
1465
+ # always ask for charge on XYZ imports
1466
+ self.always_ask_charge_checkbox.setChecked(settings_dict.get('always_ask_charge', self.default_settings.get('always_ask_charge', False)))
1467
+ # Aromatic ring circle display and torus thickness factor
1468
+ self.aromatic_circle_checkbox.setChecked(settings_dict.get('display_aromatic_circles_3d', self.default_settings.get('display_aromatic_circles_3d', False)))
1469
+ thickness_factor = float(settings_dict.get('aromatic_torus_thickness_factor', self.default_settings.get('aromatic_torus_thickness_factor', 0.6)))
1470
+ try:
1471
+ self.aromatic_torus_thickness_slider.setValue(int(thickness_factor * 100))
1472
+ self.aromatic_torus_thickness_label.setText(f"{thickness_factor:.1f}")
1473
+ except Exception:
1474
+ pass
1475
+
1476
+ # 7. 2D Settings
1477
+ bw_2d = settings_dict.get('bond_width_2d', self.default_settings['bond_width_2d'])
1478
+ self.bond_width_2d_slider.setValue(int(bw_2d * 10))
1479
+ self.bond_width_2d_label.setText(f"{bw_2d:.1f}")
1480
+
1481
+ bsd_2d = settings_dict.get('bond_spacing_double_2d', self.default_settings['bond_spacing_double_2d'])
1482
+ self.bond_spacing_double_2d_slider.setValue(int(bsd_2d * 10))
1483
+ self.bond_spacing_double_2d_label.setText(f"{bsd_2d:.1f}")
1484
+
1485
+ bst_2d = settings_dict.get('bond_spacing_triple_2d', self.default_settings['bond_spacing_triple_2d'])
1486
+ self.bond_spacing_triple_2d_slider.setValue(int(bst_2d * 10))
1487
+ self.bond_spacing_triple_2d_label.setText(f"{bst_2d:.1f}")
1488
+
1489
+ fs_2d = settings_dict.get('atom_font_size_2d', self.default_settings['atom_font_size_2d'])
1490
+ self.atom_font_size_2d_slider.setValue(int(fs_2d))
1491
+ self.atom_font_size_2d_label.setText(str(fs_2d))
1492
+
1493
+ self.atom_use_bond_color_2d_checkbox.setChecked(settings_dict.get('atom_use_bond_color_2d', self.default_settings.get('atom_use_bond_color_2d', False)))
1494
+
1495
+ self.current_bg_color_2d = settings_dict.get('background_color_2d', self.default_settings['background_color_2d'])
1496
+
1497
+ # Load Bond Cap Style
1498
+ cap_style = settings_dict.get('bond_cap_style_2d', self.default_settings.get('bond_cap_style_2d', 'Round'))
1499
+ index = self.bond_cap_style_2d_combo.findText(cap_style)
1500
+ if index >= 0:
1501
+ self.bond_cap_style_2d_combo.setCurrentIndex(index)
1502
+ self.current_bond_color_2d = settings_dict.get('bond_color_2d', self.default_settings['bond_color_2d'])
1503
+ self.update_2d_color_buttons()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MoleditPy-linux
3
- Version: 2.3.2
3
+ Version: 2.4.0
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
  License: GNU GENERAL PUBLIC LICENSE
@@ -7,25 +7,25 @@ moleditpy_linux/modules/align_plane_dialog.py,sha256=SEC32l8z2x1W1Sf1Gu001OQEtin
7
7
  moleditpy_linux/modules/alignment_dialog.py,sha256=CPubSJI52mNSXmWt7oKQ2un5lXnN8_K4Z-LMM7wWTxQ,11309
8
8
  moleditpy_linux/modules/analysis_window.py,sha256=zjP5ipSTpKw8oLr1eKdoxW8Bk1SslGlPqsVucD-x_5w,9403
9
9
  moleditpy_linux/modules/angle_dialog.py,sha256=uc2WbvSfRe892xoEirqpZ78pf2Smwzkinkso6zLWr0Y,17751
10
- moleditpy_linux/modules/atom_item.py,sha256=u8ge6B1M9sOGobfzg3tp1-EGXtEUmvdee7Fx6msg8Wk,15566
11
- moleditpy_linux/modules/bond_item.py,sha256=eVkEeKvM4igYI67DYxpey3FllqDyt_iWDo4VPYMhaPk,19137
10
+ moleditpy_linux/modules/atom_item.py,sha256=zPV6AO0Je4rKgZekyYCGehucleKP21ALDsjVhGV8n-M,17738
11
+ moleditpy_linux/modules/bond_item.py,sha256=hOwga7DcxZf8zxwZr8F7viTNgEi_9wftKFgSpROUJs8,21602
12
12
  moleditpy_linux/modules/bond_length_dialog.py,sha256=6bFPGssnqlgINuqpxLv-OhjMH3_hspnaH8QtorAyu2M,14782
13
13
  moleditpy_linux/modules/calculation_worker.py,sha256=KiGQY7i-QCQofEoE0r65KoQgpEGFcbhmxWv6egfkUdc,42324
14
14
  moleditpy_linux/modules/color_settings_dialog.py,sha256=Ow44BhCOLo0AFb6klO001k6B4drOgKX9DeNBQhZLp5o,15474
15
- moleditpy_linux/modules/constants.py,sha256=wUyC8vBN2vsqgv6G_qfzdsCX3tOihY0xDCGYB9_B1O0,4702
15
+ moleditpy_linux/modules/constants.py,sha256=hrpODQcypcLqXV8--0NiGgWRyWgEKcS6ju3Q-2RBY1w,4702
16
16
  moleditpy_linux/modules/constrained_optimization_dialog.py,sha256=REsk4ePsqNmAGPMTS_jckeM7jexrU3krwun8sKqKUCs,30062
17
17
  moleditpy_linux/modules/custom_interactor_style.py,sha256=LDNODMJoNHGe1AUSrvqv6PdeJm-hpPmSpWINppnJLt0,38942
18
18
  moleditpy_linux/modules/custom_qt_interactor.py,sha256=vCZsDfRO-FtphD5cTP7Ps-5rpHZMIGloaoe6EaKzrsw,4139
19
19
  moleditpy_linux/modules/dialog3_d_picking_mixin.py,sha256=z4udbkiX9PYmIGazPXsbftkk_oRRwZhcvlCqbyJzr24,6493
20
20
  moleditpy_linux/modules/dihedral_dialog.py,sha256=bOTDO6-b74vEDn_z6OyuBr5cRz3RnRj83PiaEBUyWJA,18002
21
- moleditpy_linux/modules/main_window.py,sha256=IL8dH3qPx2TkPgO7amuDgjlFoadh5J59xYUEVhlNZqA,36338
21
+ moleditpy_linux/modules/main_window.py,sha256=s5O-9B2MDrRg1OrXtq7rVtnqaQ9arWItGZ4Wgbn87SQ,36504
22
22
  moleditpy_linux/modules/main_window_app_state.py,sha256=8YDcGNCSpLTO1NGL9tEvNkXpUcS7JW-uK7TdUGvEqnk,35189
23
23
  moleditpy_linux/modules/main_window_compute.py,sha256=ipIkhH_DONXDnPzh7xeym9X-Yfx8EhsvXYOdyxsAj4c,53347
24
24
  moleditpy_linux/modules/main_window_dialog_manager.py,sha256=QR96LqHAPSOShXbc9cK-Ffq8a16JrXAoMKB0pHjESrQ,20072
25
25
  moleditpy_linux/modules/main_window_edit_3d.py,sha256=CUArB5wcsgq1C7LygAEC6URlbnn4RhRYDa5n-Y-etWI,19731
26
- moleditpy_linux/modules/main_window_edit_actions.py,sha256=yEc0Nw-VpN0P4e4neUu7pDuUHPGEcu6eFmwWFrSBIQ8,64815
27
- moleditpy_linux/modules/main_window_export.py,sha256=dSVfylsybDDboDuXU9Inotf6YkrKJwgBTqGYSfq1lRE,38241
28
- moleditpy_linux/modules/main_window_main_init.py,sha256=flZEzOH29Z6pplOqku51RMxgKT_QJzsjQK_EheDvjQo,93007
26
+ moleditpy_linux/modules/main_window_edit_actions.py,sha256=jjMBwCzwflEHHvf_zh_RBcup9ceR_H4dDEvdRrb6wno,69009
27
+ moleditpy_linux/modules/main_window_export.py,sha256=YVrcFaY9xffFJ6eEH6Omh8Zif8KIs6_VDW_EAzN97AA,38218
28
+ moleditpy_linux/modules/main_window_main_init.py,sha256=Qkpb2AvMADmQmdZ1190ufQ3OWwvcQTwu440riyBn9vg,94148
29
29
  moleditpy_linux/modules/main_window_molecular_parsers.py,sha256=KR6vzuqc3nutOcorpYr0QOyX3MFBcxTwDhZX96VgJ9Q,48291
30
30
  moleditpy_linux/modules/main_window_project_io.py,sha256=TWwtuKDuvgcvPZ9IGmW8r1EJJOrgxrIJRnxe_f4C1oM,17149
31
31
  moleditpy_linux/modules/main_window_string_importers.py,sha256=v47wOd4RtjKYcF-aLP-mogGGdYTpTEo3dDyAu79_5MM,10782
@@ -38,10 +38,10 @@ moleditpy_linux/modules/molecule_scene.py,sha256=khdt7h9Mk_D1cMbYeHGtq7P9aFXo0xG
38
38
  moleditpy_linux/modules/move_group_dialog.py,sha256=Fyuy3Uq1KsFsk9qR96r_FxPbAM_-zSfW2dsMQGv7btc,27276
39
39
  moleditpy_linux/modules/periodic_table_dialog.py,sha256=ItEZUts1XCietz9paY-spvbzxh6SXak3GnikwqkHZCw,4006
40
40
  moleditpy_linux/modules/planarize_dialog.py,sha256=eaqI1MpF35e-VUMpJATt-EtGG5FhcSUlbAenUaFGabY,8593
41
- moleditpy_linux/modules/plugin_interface.py,sha256=8_keZsQ6pZEVuEviHXZWlLFs8Yt6J83mwApbX1YL7P0,7791
42
- moleditpy_linux/modules/plugin_manager.py,sha256=4lXv9stNiiqgY2FRdNDzB41jLcOfc0VzAIF88eMYWlY,20369
41
+ moleditpy_linux/modules/plugin_interface.py,sha256=JfobFdAOLp5OnyJp3BNsWnpmZNJYypWQHNVNT0fP1N8,8171
42
+ moleditpy_linux/modules/plugin_manager.py,sha256=ZFQz8VlCy1_IHX7DnMro7iRbouB_rxZQowfrKuZ76G8,21133
43
43
  moleditpy_linux/modules/plugin_manager_window.py,sha256=b4kEv0DaWHZG76ZaFTOxn6CVtA62_0MpPYYr10ehCtA,12544
44
- moleditpy_linux/modules/settings_dialog.py,sha256=Nr7yE8UmYRi3VObWvRlrnv0DnjSjmYXbvqryZ02O12k,65348
44
+ moleditpy_linux/modules/settings_dialog.py,sha256=kT1c4INl0EOlazDRv1WZbpg2AHEPfworyq8BO-6z79k,85511
45
45
  moleditpy_linux/modules/template_preview_item.py,sha256=djdq3tz73d_fJGOvai3E-V9Hk9q9ZW7skx7BV59mooA,6556
46
46
  moleditpy_linux/modules/template_preview_view.py,sha256=4OCHZDO51BvJpKdfrBWJ4_4WfLfFSKxsVIyf7I-Kj2E,3350
47
47
  moleditpy_linux/modules/translation_dialog.py,sha256=x_GJsbVk-cj4aN2KgmYWDRUDInFlXezAoYoTvX-OT30,14553
@@ -51,9 +51,9 @@ moleditpy_linux/modules/assets/file_icon.ico,sha256=yyVj084A7HuMNbV073cE_Ag3Ne40
51
51
  moleditpy_linux/modules/assets/icon.icns,sha256=wD5R6-Vw7K662tVKhu2E1ImN0oUuyAP4youesEQsn9c,139863
52
52
  moleditpy_linux/modules/assets/icon.ico,sha256=RfgFcx7-dHY_2STdsOQCQziY5SNhDr3gPnjO6jzEDPI,147975
53
53
  moleditpy_linux/modules/assets/icon.png,sha256=kCFN1WacYIdy0GN6SFEbNA00ef39pCczBnFdkkBI8Bs,147110
54
- moleditpy_linux-2.3.2.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
55
- moleditpy_linux-2.3.2.dist-info/METADATA,sha256=du5rtSINQNFLeLYeJq8hlt5p62l-AMEWoDADk4-2h1o,60708
56
- moleditpy_linux-2.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
57
- moleditpy_linux-2.3.2.dist-info/entry_points.txt,sha256=-OzipSi__yVwlimNtu3eiRP5t5UMg55Cs0udyhXYiyw,60
58
- moleditpy_linux-2.3.2.dist-info/top_level.txt,sha256=qyqe-hDYL6CXyin9E5Me5rVl3PG84VqiOjf9bQvfJLs,16
59
- moleditpy_linux-2.3.2.dist-info/RECORD,,
54
+ moleditpy_linux-2.4.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
55
+ moleditpy_linux-2.4.0.dist-info/METADATA,sha256=PQ4fwQhd4wuV-maVTsucTP_88GLk0EQhZi8UUhw_2P0,60708
56
+ moleditpy_linux-2.4.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
57
+ moleditpy_linux-2.4.0.dist-info/entry_points.txt,sha256=-OzipSi__yVwlimNtu3eiRP5t5UMg55Cs0udyhXYiyw,60
58
+ moleditpy_linux-2.4.0.dist-info/top_level.txt,sha256=qyqe-hDYL6CXyin9E5Me5rVl3PG84VqiOjf9bQvfJLs,16
59
+ moleditpy_linux-2.4.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5