moleditpy 1.9.2__tar.gz → 1.9.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: moleditpy
3
- Version: 1.9.2
3
+ Version: 1.9.3
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
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "moleditpy"
7
7
 
8
- version = "1.9.2"
8
+ version = "1.9.3"
9
9
 
10
10
  authors = [
11
11
  { name="HiroYokoyama", email="titech.yoko.hiro@gmail.com" },
@@ -11,7 +11,7 @@ DOI 10.5281/zenodo.17268532
11
11
  """
12
12
 
13
13
  #Version
14
- VERSION = '1.9.2'
14
+ VERSION = '1.9.3'
15
15
 
16
16
  print("-----------------------------------------------------")
17
17
  print("MoleditPy — A Python-based molecular editing software")
@@ -4315,7 +4315,6 @@ class MoleculeScene(QGraphicsScene):
4315
4315
  super().mouseMoveEvent(event)
4316
4316
 
4317
4317
  def mouseReleaseEvent(self, event):
4318
-
4319
4318
  if not self.window.is_2d_editable:
4320
4319
  return
4321
4320
 
@@ -4329,22 +4328,19 @@ class MoleculeScene(QGraphicsScene):
4329
4328
  if self.mode.startswith('template') and is_click:
4330
4329
  if self.template_context and self.template_context.get('points'):
4331
4330
  context = self.template_context
4332
-
4333
4331
  # Check if this is a user template
4334
4332
  if self.mode.startswith('template_user'):
4335
4333
  self.add_user_template_fragment(context)
4336
4334
  else:
4337
4335
  self.add_molecule_fragment(context['points'], context['bonds_info'], existing_items=context.get('items', []))
4338
-
4339
4336
  self.data_changed_in_event = True
4340
-
4341
4337
  # イベント処理をここで完了させ、下のアイテムが選択されるのを防ぐ
4342
4338
  self.start_atom=None; self.start_pos = None; self.press_pos = None
4343
4339
  if self.data_changed_in_event: self.window.push_undo_state()
4344
4340
  return
4345
4341
 
4346
4342
  released_item = self.itemAt(end_pos, self.views()[0].transform())
4347
-
4343
+
4348
4344
  # 1. 特殊モード(ラジカル/電荷)の処理
4349
4345
  if (self.mode == 'radical') and is_click and isinstance(released_item, AtomItem):
4350
4346
  atom = released_item
@@ -4371,7 +4367,6 @@ class MoleculeScene(QGraphicsScene):
4371
4367
 
4372
4368
  elif self.mode.startswith('bond') and is_click and isinstance(released_item, BondItem):
4373
4369
  b = released_item
4374
-
4375
4370
  if self.mode == 'bond_2_5':
4376
4371
  try:
4377
4372
  if b.order == 2:
@@ -4391,53 +4386,40 @@ class MoleculeScene(QGraphicsScene):
4391
4386
  if hasattr(self.window, 'statusBar'):
4392
4387
  self.window.statusBar().showMessage(f"Error changing E/Z stereochemistry: {e}", 5000)
4393
4388
  return # この後の処理は行わない
4394
-
4395
4389
  elif self.bond_stereo != 0 and b.order == self.bond_order and b.stereo == self.bond_stereo:
4396
4390
  # 方向性を反転させる
4397
4391
  old_id1, old_id2 = b.atom1.atom_id, b.atom2.atom_id
4398
-
4399
4392
  # 1. 古い方向の結合をデータから削除
4400
4393
  self.data.remove_bond(old_id1, old_id2)
4401
-
4402
4394
  # 2. 逆方向で結合をデータに再追加
4403
4395
  new_key, _ = self.data.add_bond(old_id2, old_id1, self.bond_order, self.bond_stereo)
4404
-
4405
4396
  # 3. BondItemの原子参照を入れ替え、新しいデータと関連付ける
4406
4397
  b.atom1, b.atom2 = b.atom2, b.atom1
4407
4398
  self.data.bonds[new_key]['item'] = b
4408
-
4409
4399
  # 4. 見た目を更新
4410
4400
  b.update_position()
4411
-
4412
4401
  else:
4413
4402
  # 既存の結合を一度削除
4414
4403
  self.data.remove_bond(b.atom1.atom_id, b.atom2.atom_id)
4415
-
4416
4404
  # BondItemが記憶している方向(b.atom1 -> b.atom2)で、新しい結合様式を再作成
4417
4405
  # これにより、修正済みのadd_bondが呼ばれ、正しい方向で保存される
4418
4406
  new_key, _ = self.data.add_bond(b.atom1.atom_id, b.atom2.atom_id, self.bond_order, self.bond_stereo)
4419
-
4420
4407
  # BondItemの見た目とデータ参照を更新
4421
4408
  b.prepareGeometryChange()
4422
4409
  b.order = self.bond_order
4423
4410
  b.stereo = self.bond_stereo
4424
4411
  self.data.bonds[new_key]['item'] = b
4425
4412
  b.update()
4426
-
4427
4413
  self.clearSelection()
4428
4414
  self.data_changed_in_event = True
4429
-
4430
4415
  # 3. 新規原子・結合の作成処理 (atom_* モード および すべての bond_* モードで許可)
4431
4416
  elif self.start_atom and (self.mode.startswith('atom') or self.mode.startswith('bond')):
4432
4417
  line = QLineF(self.start_atom.pos(), end_pos); end_item = self.itemAt(end_pos, self.views()[0].transform())
4433
-
4434
4418
  # 使用する結合様式を決定
4435
4419
  # atomモードの場合は bond_order/stereo を None にして create_bond にデフォルト値(1, 0)を適用
4436
4420
  # bond_* モードの場合は現在の設定 (self.bond_order/stereo) を使用
4437
4421
  order_to_use = self.bond_order if self.mode.startswith('bond') else None
4438
4422
  stereo_to_use = self.bond_stereo if self.mode.startswith('bond') else None
4439
-
4440
-
4441
4423
  if is_click:
4442
4424
  # 短いクリック: 既存原子のシンボル更新 (atomモードのみ)
4443
4425
  if self.mode.startswith('atom') and self.start_atom.symbol != self.current_atom_symbol:
@@ -4451,25 +4433,20 @@ class MoleculeScene(QGraphicsScene):
4451
4433
  new_id = self.create_atom(self.current_atom_symbol, end_pos); new_item = self.data.atoms[new_id]['item']
4452
4434
  self.create_bond(self.start_atom, new_item, bond_order=order_to_use, bond_stereo=stereo_to_use)
4453
4435
  self.data_changed_in_event = True
4454
-
4455
4436
  # 4. 空白領域からの新規作成処理 (atom_* モード および すべての bond_* モードで許可)
4456
4437
  elif self.start_pos and (self.mode.startswith('atom') or self.mode.startswith('bond')):
4457
4438
  line = QLineF(self.start_pos, end_pos)
4458
-
4459
4439
  # 使用する結合様式を決定
4460
4440
  order_to_use = self.bond_order if self.mode.startswith('bond') else None
4461
4441
  stereo_to_use = self.bond_stereo if self.mode.startswith('bond') else None
4462
-
4463
4442
  if line.length() < 10:
4464
4443
  self.create_atom(self.current_atom_symbol, end_pos); self.data_changed_in_event = True
4465
4444
  else:
4466
4445
  end_item = self.itemAt(end_pos, self.views()[0].transform())
4467
-
4468
4446
  if isinstance(end_item, AtomItem):
4469
4447
  start_id = self.create_atom(self.current_atom_symbol, self.start_pos)
4470
4448
  start_item = self.data.atoms[start_id]['item']
4471
4449
  self.create_bond(start_item, end_item, bond_order=order_to_use, bond_stereo=stereo_to_use)
4472
-
4473
4450
  else:
4474
4451
  start_id = self.create_atom(self.current_atom_symbol, self.start_pos)
4475
4452
  end_id = self.create_atom(self.current_atom_symbol, end_pos)
@@ -4480,11 +4457,9 @@ class MoleculeScene(QGraphicsScene):
4480
4457
  bond_stereo=stereo_to_use
4481
4458
  )
4482
4459
  self.data_changed_in_event = True
4483
-
4484
4460
  # 5. それ以外の処理 (Selectモードなど)
4485
4461
  else: super().mouseReleaseEvent(event)
4486
4462
 
4487
-
4488
4463
  # 削除されたオブジェクトを安全にチェック
4489
4464
  moved_atoms = []
4490
4465
  for item, old_pos in self.initial_positions_in_event.items():
@@ -4495,7 +4470,6 @@ class MoleculeScene(QGraphicsScene):
4495
4470
  except RuntimeError:
4496
4471
  # オブジェクトが削除されている場合はスキップ
4497
4472
  continue
4498
-
4499
4473
  if moved_atoms:
4500
4474
  self.data_changed_in_event = True
4501
4475
  bonds_to_update = set()
@@ -4507,12 +4481,9 @@ class MoleculeScene(QGraphicsScene):
4507
4481
  # オブジェクトが削除されている場合はスキップ
4508
4482
  continue
4509
4483
  for bond in bonds_to_update: bond.update_position()
4510
-
4511
4484
  # 原子移動後に測定ラベルの位置を更新
4512
4485
  self.window.update_2d_measurement_labels()
4513
-
4514
4486
  if self.views(): self.views()[0].viewport().update()
4515
-
4516
4487
  self.start_atom=None; self.start_pos = None; self.press_pos = None; self.temp_line = None
4517
4488
  self.template_context = {}
4518
4489
  # Clear user template data when switching modes
@@ -7009,7 +6980,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
7009
6980
  if abs(current_pos[0] - self._mouse_press_pos[0]) > 3 or abs(current_pos[1] - self._mouse_press_pos[1]) > 3:
7010
6981
  self._mouse_moved_during_drag = True
7011
6982
 
7012
- if self._is_dragging_atom:
6983
+ if self._is_dragging_atom and mw.dragged_atom_info is not None:
7013
6984
  # カスタムの原子ドラッグ処理
7014
6985
  self.is_dragging = True
7015
6986
  atom_id = mw.dragged_atom_info['id']
@@ -7075,6 +7046,72 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
7075
7046
  mw.draw_molecule_3d(mw.current_mol)
7076
7047
  mw.push_undo_state()
7077
7048
  mw.dragged_atom_info = None
7049
+ # Always ensure the visual representation matches the actual
7050
+ # RDKit conformer coordinates when the mouse is released.
7051
+ try:
7052
+ if mw.current_mol and mw.current_mol.GetNumConformers() > 0:
7053
+ conf = mw.current_mol.GetConformer()
7054
+ # Rebuild the authoritative atom_positions_3d from the conformer
7055
+ mw.atom_positions_3d = np.array([list(conf.GetAtomPosition(i)) for i in range(mw.current_mol.GetNumAtoms())])
7056
+
7057
+ # If a glyph_source exists (typical), update its points and mark modified
7058
+ if hasattr(mw, 'glyph_source') and mw.glyph_source is not None:
7059
+ try:
7060
+ mw.glyph_source.points = mw.atom_positions_3d
7061
+ mw.glyph_source.Modified()
7062
+ except Exception:
7063
+ # Fallback to a full redraw if updating glyph_source fails
7064
+ try:
7065
+ mw.draw_molecule_3d(mw.current_mol)
7066
+ except Exception:
7067
+ pass
7068
+
7069
+ # Refresh overlays and labels that depend on atom_positions_3d
7070
+ try:
7071
+ mw.update_3d_selection_display()
7072
+ except Exception:
7073
+ pass
7074
+ try:
7075
+ mw.update_measurement_labels_display()
7076
+ except Exception:
7077
+ pass
7078
+ try:
7079
+ mw.update_2d_measurement_labels()
7080
+ except Exception:
7081
+ pass
7082
+ try:
7083
+ mw.show_all_atom_info()
7084
+ except Exception:
7085
+ pass
7086
+
7087
+ # Final render to make sure everything is consistent
7088
+ try:
7089
+ if hasattr(mw, 'plotter') and mw.plotter:
7090
+ mw.plotter.render()
7091
+ except Exception:
7092
+ pass
7093
+ # As a final safety-net, ensure the visual scene exactly matches
7094
+ # the authoritative RDKit conformer coordinates. This forces a
7095
+ # full redraw if any lower-level updates failed to sync.
7096
+ try:
7097
+ if mw.current_mol and mw.current_mol.GetNumConformers() > 0:
7098
+ # Redraw the molecule and render to guarantee consistency
7099
+ try:
7100
+ mw.draw_molecule_3d(mw.current_mol)
7101
+ except Exception:
7102
+ # If a full redraw fails, at least attempt a render
7103
+ pass
7104
+ try:
7105
+ if hasattr(mw, 'plotter') and mw.plotter:
7106
+ mw.plotter.render()
7107
+ except Exception:
7108
+ pass
7109
+ except Exception:
7110
+ # Silently ignore any errors during this final sync step
7111
+ pass
7112
+ except Exception:
7113
+ # Do not allow a failure here to interrupt release flow
7114
+ pass
7078
7115
  else:
7079
7116
  # カメラ回転の後始末を親クラスに任せます
7080
7117
  super().OnLeftButtonUp()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: moleditpy
3
- Version: 1.9.2
3
+ Version: 1.9.3
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