MoleditPy 2.4.6__py3-none-any.whl → 2.4.8__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.
@@ -164,43 +164,50 @@ class BondItem(QGraphicsItem):
164
164
  return rect
165
165
 
166
166
  def shape(self):
167
+ """Define the precise collision/selection area, separate from the drawing area (boundingRect)."""
167
168
  path = QPainterPath()
168
169
  try:
169
170
  line = self.get_line_in_local_coords()
171
+ # Create a simple path along the bond line
172
+ path.moveTo(line.p1())
173
+ path.lineTo(line.p2())
174
+
175
+ # Stroke it to give it some width (e.g., 10px or dynamic based on settings) generally easier to click
176
+ # even if the visual width is smaller.
177
+ stroker = QPainterPathStroker()
178
+ stroker.setWidth(DESIRED_BOND_PIXEL_WIDTH) # Use constant (20.0)
179
+ path = stroker.createStroke(path)
180
+
181
+ # If there's an E/Z label, add its rect to the selection shape
182
+ label_rect = self.get_ez_label_local_rect()
183
+ if label_rect:
184
+ path.addRect(label_rect)
185
+
170
186
  except Exception:
171
- return path
172
- if line.length() == 0:
173
- return path
174
-
175
- scene = self.scene()
176
- if not scene or not scene.views():
177
- return super().shape()
178
-
179
- view = scene.views()[0]
180
- scale = view.transform().m11()
187
+ # Fallback to a small rect around the origin if calculation fails
188
+ path.addRect(QRectF(-5, -5, 10, 10))
189
+
190
+ return path
181
191
 
182
- # Dynamic bond width
183
- width_2d = 2.0
192
+ def get_ez_label_local_rect(self):
193
+ """Helper to get E/Z label rect in local coordinates."""
194
+ if self.order != 2 or self.stereo not in [3, 4]:
195
+ return None
184
196
  try:
185
- if view.window() and hasattr(view.window(), 'settings'):
186
- width_2d = view.window().settings.get('bond_width_2d', 2.0)
197
+ line = self.get_line_in_local_coords()
198
+ center = line.center()
199
+
200
+ # Logic similar to boundingRect but returning just the label box
201
+ font_size = 20
202
+ # ... (Simpler logic: just return a box around center)
203
+ # Standard size estimate
204
+ box_size = 30
205
+ return QRectF(center.x() - box_size/2, center.y() - box_size/2, box_size, box_size)
187
206
  except Exception:
188
- pass
189
-
190
- # Hit area should be roughly closely matched or slightly larger than visual
191
- # Ensure minimum hit width for usability
192
- scene_width = max(DESIRED_BOND_PIXEL_WIDTH, width_2d * 10) / scale
207
+ return None
193
208
 
194
209
 
195
- stroker = QPainterPathStroker()
196
- stroker.setWidth(scene_width)
197
- stroker.setCapStyle(Qt.PenCapStyle.RoundCap)
198
- stroker.setJoinStyle(Qt.PenJoinStyle.RoundJoin)
199
210
 
200
- center_line_path = QPainterPath(line.p1())
201
- center_line_path.lineTo(line.p2())
202
-
203
- return stroker.createStroke(center_line_path)
204
211
 
205
212
  def paint(self, painter, option, widget):
206
213
  if self.atom1 is None or self.atom2 is None:
@@ -16,7 +16,7 @@ from PyQt6.QtGui import QFont, QColor
16
16
  from rdkit import Chem
17
17
 
18
18
  #Version
19
- VERSION = '2.4.6'
19
+ VERSION = '2.4.8'
20
20
 
21
21
  ATOM_RADIUS = 18
22
22
  BOND_OFFSET = 3.5
@@ -109,7 +109,21 @@ class MainWindowAppState(object):
109
109
 
110
110
  state['version'] = VERSION
111
111
 
112
- if self.current_mol: state['mol_3d'] = self.current_mol.ToBinary()
112
+ if self.current_mol:
113
+ state['mol_3d'] = self.current_mol.ToBinary()
114
+ # RDKit binary serialization does not preserve custom properties like _original_atom_id.
115
+ # We store them separately to ensure we can restore the 2D-3D link after undo/redo.
116
+ mol_3d_atom_ids = []
117
+ for i in range(self.current_mol.GetNumAtoms()):
118
+ atom = self.current_mol.GetAtomWithIdx(i)
119
+ try:
120
+ if atom.HasProp("_original_atom_id"):
121
+ mol_3d_atom_ids.append(atom.GetIntProp("_original_atom_id"))
122
+ else:
123
+ mol_3d_atom_ids.append(None)
124
+ except Exception:
125
+ mol_3d_atom_ids.append(None)
126
+ state['mol_3d_atom_ids'] = mol_3d_atom_ids
113
127
 
114
128
  state['is_3d_viewer_mode'] = not self.is_2d_editable
115
129
 
@@ -204,6 +218,27 @@ class MainWindowAppState(object):
204
218
  self.current_mol = Chem.Mol(loaded_data['mol_3d'])
205
219
  # デバッグ:3D構造が有効かチェック
206
220
  if self.current_mol and self.current_mol.GetNumAtoms() > 0:
221
+ # Restore _original_atom_id if present in saved state
222
+ if 'mol_3d_atom_ids' in loaded_data:
223
+ atom_ids = loaded_data['mol_3d_atom_ids']
224
+ if len(atom_ids) == self.current_mol.GetNumAtoms():
225
+ for i, aid in enumerate(atom_ids):
226
+ if aid is not None:
227
+ try:
228
+ self.current_mol.GetAtomWithIdx(i).SetIntProp("_original_atom_id", int(aid))
229
+ except Exception:
230
+ pass
231
+
232
+ # Re-create atom ID mapping to synchronize 2D atoms with 3D actors
233
+ # This MUST be done before draw_molecule_3d for labels to be correct.
234
+ try:
235
+ self.create_atom_id_mapping()
236
+ self.update_atom_id_menu_text()
237
+ self.update_atom_id_menu_state()
238
+ except Exception:
239
+ pass
240
+
241
+ # draw_molecule_3d will use the restored IDs for labels/picking if show_all_atom_info is called.
207
242
  self.draw_molecule_3d(self.current_mol)
208
243
  self.plotter.reset_camera()
209
244
  # 3D関連機能を統一的に有効化
@@ -254,7 +289,11 @@ class MainWindowAppState(object):
254
289
  'atoms': {k: (v['symbol'], v['item'].pos().x(), v['item'].pos().y(), v.get('charge', 0), v.get('radical', 0)) for k, v in self.data.atoms.items()},
255
290
  'bonds': {k: (v['order'], v.get('stereo', 0)) for k, v in self.data.bonds.items()},
256
291
  '_next_atom_id': self.data._next_atom_id,
257
- 'mol_3d': self.current_mol.ToBinary() if self.current_mol else None
292
+ 'mol_3d': self.current_mol.ToBinary() if self.current_mol else None,
293
+ 'mol_3d_atom_ids': [
294
+ (a.GetIntProp("_original_atom_id") if a.HasProp("_original_atom_id") else None)
295
+ for a in self.current_mol.GetAtoms()
296
+ ] if self.current_mol else None
258
297
  }
259
298
 
260
299
  last_state_for_comparison = None
@@ -266,7 +305,8 @@ class MainWindowAppState(object):
266
305
  'atoms': {k: (v['symbol'], v['pos'][0], v['pos'][1], v.get('charge', 0), v.get('radical', 0)) for k, v in last_atoms.items()},
267
306
  'bonds': {k: (v['order'], v.get('stereo', 0)) for k, v in last_bonds.items()},
268
307
  '_next_atom_id': last_state.get('_next_atom_id'),
269
- 'mol_3d': last_state.get('mol_3d', None)
308
+ 'mol_3d': last_state.get('mol_3d', None),
309
+ 'mol_3d_atom_ids': last_state.get('mol_3d_atom_ids', None)
270
310
  }
271
311
 
272
312
  if not last_state_for_comparison or current_state_for_comparison != last_state_for_comparison:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MoleditPy
3
- Version: 2.4.6
3
+ Version: 2.4.8
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
@@ -8,18 +8,18 @@ moleditpy/modules/alignment_dialog.py,sha256=CPubSJI52mNSXmWt7oKQ2un5lXnN8_K4Z-L
8
8
  moleditpy/modules/analysis_window.py,sha256=zjP5ipSTpKw8oLr1eKdoxW8Bk1SslGlPqsVucD-x_5w,9403
9
9
  moleditpy/modules/angle_dialog.py,sha256=uc2WbvSfRe892xoEirqpZ78pf2Smwzkinkso6zLWr0Y,17751
10
10
  moleditpy/modules/atom_item.py,sha256=9x0xojz6eih-N6UFgGJHNdRshgOurx3F5ysMzcHup6Y,18034
11
- moleditpy/modules/bond_item.py,sha256=jEg2CNMBrq74XihWRULzUTgN9g8k4IdT3AS_a2JPpFM,23644
11
+ moleditpy/modules/bond_item.py,sha256=21oX1sR5kpY-MlfUsmekkV6XATVwIAhmsH1UMwV34rg,24225
12
12
  moleditpy/modules/bond_length_dialog.py,sha256=6bFPGssnqlgINuqpxLv-OhjMH3_hspnaH8QtorAyu2M,14782
13
13
  moleditpy/modules/calculation_worker.py,sha256=KiGQY7i-QCQofEoE0r65KoQgpEGFcbhmxWv6egfkUdc,42324
14
14
  moleditpy/modules/color_settings_dialog.py,sha256=Ow44BhCOLo0AFb6klO001k6B4drOgKX9DeNBQhZLp5o,15474
15
- moleditpy/modules/constants.py,sha256=8qXgVTGzHGMtCAXYqkRNrXWxS0TEpAKjY6am42-YF9Y,4702
15
+ moleditpy/modules/constants.py,sha256=M1dGZR8B9RNXfzFq1GhX7lUxMmZn1b_Whtu9ShCQ6wk,4702
16
16
  moleditpy/modules/constrained_optimization_dialog.py,sha256=REsk4ePsqNmAGPMTS_jckeM7jexrU3krwun8sKqKUCs,30062
17
17
  moleditpy/modules/custom_interactor_style.py,sha256=LDNODMJoNHGe1AUSrvqv6PdeJm-hpPmSpWINppnJLt0,38942
18
18
  moleditpy/modules/custom_qt_interactor.py,sha256=vCZsDfRO-FtphD5cTP7Ps-5rpHZMIGloaoe6EaKzrsw,4139
19
19
  moleditpy/modules/dialog3_d_picking_mixin.py,sha256=z4udbkiX9PYmIGazPXsbftkk_oRRwZhcvlCqbyJzr24,6493
20
20
  moleditpy/modules/dihedral_dialog.py,sha256=bOTDO6-b74vEDn_z6OyuBr5cRz3RnRj83PiaEBUyWJA,18002
21
21
  moleditpy/modules/main_window.py,sha256=w52L0F7V3b0BiNhQImbulZ9N8cRKolYy06szfoMOIcg,36830
22
- moleditpy/modules/main_window_app_state.py,sha256=8YDcGNCSpLTO1NGL9tEvNkXpUcS7JW-uK7TdUGvEqnk,35189
22
+ moleditpy/modules/main_window_app_state.py,sha256=Civ28scAuIPXdD1qix5IkUAgEgl4Wy3wCqblomF3EY4,37493
23
23
  moleditpy/modules/main_window_compute.py,sha256=ipIkhH_DONXDnPzh7xeym9X-Yfx8EhsvXYOdyxsAj4c,53347
24
24
  moleditpy/modules/main_window_dialog_manager.py,sha256=QR96LqHAPSOShXbc9cK-Ffq8a16JrXAoMKB0pHjESrQ,20072
25
25
  moleditpy/modules/main_window_edit_3d.py,sha256=CUArB5wcsgq1C7LygAEC6URlbnn4RhRYDa5n-Y-etWI,19731
@@ -51,9 +51,9 @@ moleditpy/modules/assets/file_icon.ico,sha256=yyVj084A7HuMNbV073cE_Ag3Ne405qgOP3
51
51
  moleditpy/modules/assets/icon.icns,sha256=wD5R6-Vw7K662tVKhu2E1ImN0oUuyAP4youesEQsn9c,139863
52
52
  moleditpy/modules/assets/icon.ico,sha256=RfgFcx7-dHY_2STdsOQCQziY5SNhDr3gPnjO6jzEDPI,147975
53
53
  moleditpy/modules/assets/icon.png,sha256=kCFN1WacYIdy0GN6SFEbNA00ef39pCczBnFdkkBI8Bs,147110
54
- moleditpy-2.4.6.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
55
- moleditpy-2.4.6.dist-info/METADATA,sha256=UR9T-fjKgryzsDETGIPYZmpCG4Fs-ghP-94vjjvmiQs,60629
56
- moleditpy-2.4.6.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
57
- moleditpy-2.4.6.dist-info/entry_points.txt,sha256=yH1h9JjALhok1foXT3-hYrC4ufoZt8b7oiBcsdnGNNM,54
58
- moleditpy-2.4.6.dist-info/top_level.txt,sha256=ARICrS4ihlPXqywlKl6o-oJa3Qz3gZRWu_VZsQ3_c44,10
59
- moleditpy-2.4.6.dist-info/RECORD,,
54
+ moleditpy-2.4.8.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
55
+ moleditpy-2.4.8.dist-info/METADATA,sha256=dXEV8a4OwiNRfpQ0vg6EkSN9j1hqLf4j63xJJ3FSsto,60629
56
+ moleditpy-2.4.8.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
57
+ moleditpy-2.4.8.dist-info/entry_points.txt,sha256=yH1h9JjALhok1foXT3-hYrC4ufoZt8b7oiBcsdnGNNM,54
58
+ moleditpy-2.4.8.dist-info/top_level.txt,sha256=ARICrS4ihlPXqywlKl6o-oJa3Qz3gZRWu_VZsQ3_c44,10
59
+ moleditpy-2.4.8.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.10.1)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5