chartflow 0.1.4__tar.gz → 0.1.5__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.
- {chartflow-0.1.4 → chartflow-0.1.5}/PKG-INFO +1 -1
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/flow/chart.py +2 -1
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/_node_base.py +29 -2
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/_node_interface.py +3 -1
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/_node_ports.py +49 -30
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/connection_item.py +1877 -1636
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/enums.py +1 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/node_canvas.py +22 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/node_item.py +59 -3
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/popmenu.py +27 -2
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/port_item.py +1 -1
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/test_qnode.py +283 -3
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow.egg-info/PKG-INFO +1 -1
- {chartflow-0.1.4 → chartflow-0.1.5}/setup.py +1 -1
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/__init__.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/_verify_importexport.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/edit/__init__.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/edit/code_editor.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/edit/code_folder.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/edit/completer.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/edit/context_analyzer.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/edit/demo.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/edit/line_number_area.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/edit/minimap.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/edit/python_editor.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/edit/qcompletion.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/edit/syntax_highlighter.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/edit/test_editor.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/flow/__init__.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/flow/_demo.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/flow/editor.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/flow/flow.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/flow/qchart.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/flow/test/__init__.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/flow/test/test_chart.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/flow/test_decorator_sync.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/__init__.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/decorators.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/demo.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/demo_pyqt6.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/demo_tree.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/logic.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/meta.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/scheduler.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/stage.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/stdio.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/test/__init__.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/test/test_decorators.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/test/test_events.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/test/test_integration.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/test/test_logic.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/test/test_meta.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/test/test_runtime_param.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/test/test_scheduler.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/test/test_stage.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/test_behavior_events.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/test_nested_parallel.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/test_stage_spec.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/fsm/utils.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/__init__.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/_connection_interface.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/_node_interaction.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/_node_markers.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/_port_interface.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/arrow_item.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/color_utils.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/demo.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/demo_arrow_drag.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/demo_decorator.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/demo_ports.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/example.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/layout_utils.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/main_window.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/menu_bar.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/qconnection_editor.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/qnode_editor.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/selection_rect.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/style_panel.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/node/styles.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/showcase.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow/theme.py +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow.egg-info/SOURCES.txt +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow.egg-info/dependency_links.txt +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow.egg-info/requires.txt +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/chartflow.egg-info/top_level.txt +0 -0
- {chartflow-0.1.4 → chartflow-0.1.5}/setup.cfg +0 -0
|
@@ -398,7 +398,10 @@ class _NodeCore(QGraphicsEllipseItem):
|
|
|
398
398
|
|
|
399
399
|
- 名称需容纳在内部区域(INNER_RATIO 以内); 有 hit 时额外预留
|
|
400
400
|
(dot 深度 + 间距 + hit 文本宽度), 避免 hit 与名称重叠。
|
|
401
|
-
- 矩形且有端口时: 宽度由名称(+hit)决定, 高度由最多端口的一侧决定
|
|
401
|
+
- 矩形且有端口时: 宽度由名称(+hit)决定, 高度由最多端口的一侧决定
|
|
402
|
+
(可长宽不等, 但宽不短于高——顶/底边不短于左右边)。
|
|
403
|
+
- 六边形: 平顶/平底边长(=rw)须不短于斜边长 hypot(rw/2, rh),
|
|
404
|
+
即 rw >= rh * 2/√3, 按此加宽(名称短时正方形不满足该约束)。
|
|
402
405
|
- 其余(椭圆/菱形/无端口矩形): 保持方形。
|
|
403
406
|
"""
|
|
404
407
|
tw, th = self._nameTextSize()
|
|
@@ -411,6 +414,8 @@ class _NodeCore(QGraphicsEllipseItem):
|
|
|
411
414
|
port_extent = self._portInwardExtent()
|
|
412
415
|
rw = max(rw_name, name_half + port_extent + NAME_INNER_PAD, self._min_radius)
|
|
413
416
|
rh = max(rh_name, self._halfHeightForPorts(rw), self._min_radius)
|
|
417
|
+
# 矩形: 顶/底边(宽)不短于左右边(高); 名称短/端口多导致超高时加宽
|
|
418
|
+
rw = max(rw, rh)
|
|
414
419
|
return rw, rh
|
|
415
420
|
|
|
416
421
|
# 方形
|
|
@@ -421,6 +426,10 @@ class _NodeCore(QGraphicsEllipseItem):
|
|
|
421
426
|
R = max(R, self._circlePortRadius())
|
|
422
427
|
else:
|
|
423
428
|
R = max(R, self._halfHeightForPorts(R))
|
|
429
|
+
if self._shape == NodeShape.HEXAGON:
|
|
430
|
+
# 六边形: 顶/底边 >= 斜边 -> rw >= rh * 2/√3; 加宽而非压扁,
|
|
431
|
+
# 以保留端口/名称的纵向空间
|
|
432
|
+
return R * 2.0 / math.sqrt(3.0), R
|
|
424
433
|
return R, R
|
|
425
434
|
|
|
426
435
|
def _setSize(self, rw: float, rh: float) -> None:
|
|
@@ -429,6 +438,12 @@ class _NodeCore(QGraphicsEllipseItem):
|
|
|
429
438
|
# 圆形节点固定为标准圆(宽高相等), 保证端口沿弧线布局正确
|
|
430
439
|
side = max(rw, rh)
|
|
431
440
|
rw = rh = side
|
|
441
|
+
elif self._shape == NodeShape.HEXAGON:
|
|
442
|
+
# 六边形: 顶/底边 >= 斜边 -> rw >= rh * 2/√3(兜底, 含自定义半径路径)
|
|
443
|
+
rw = max(rw, rh * 2.0 / math.sqrt(3.0))
|
|
444
|
+
elif self._shape == NodeShape.RECTANGLE:
|
|
445
|
+
# 矩形: 顶/底边(宽)不短于左右边(高)(兜底)
|
|
446
|
+
rw = max(rw, rh)
|
|
432
447
|
self._rw = rw
|
|
433
448
|
self._rh = rh
|
|
434
449
|
self._radius = rw # 兼容椭圆/菱形及旧代码
|
|
@@ -475,6 +490,7 @@ class _NodeCore(QGraphicsEllipseItem):
|
|
|
475
490
|
- ELLIPSE: 圆(半径 + 外环)
|
|
476
491
|
- RECTANGLE: 圆角矩形(外扩外环)
|
|
477
492
|
- DIAMOND: 菱形(四顶点外扩外环)
|
|
493
|
+
- HEXAGON: 六边形(六顶点外扩外环)
|
|
478
494
|
"""
|
|
479
495
|
rect = self.rect()
|
|
480
496
|
m = self._hitShapeMargin()
|
|
@@ -488,7 +504,7 @@ class _NodeCore(QGraphicsEllipseItem):
|
|
|
488
504
|
# 圆角半径与 paint() 一致(基于原始 rect, 否则圆角位置错位)
|
|
489
505
|
corner = min(rect.width(), rect.height()) * 0.2
|
|
490
506
|
path.addRoundedRect(outer, corner, corner)
|
|
491
|
-
|
|
507
|
+
elif self._shape == NodeShape.DIAMOND:
|
|
492
508
|
c = rect.center()
|
|
493
509
|
hw = rect.width() / 2.0 + m
|
|
494
510
|
hh = rect.height() / 2.0 + m
|
|
@@ -497,6 +513,17 @@ class _NodeCore(QGraphicsEllipseItem):
|
|
|
497
513
|
path.lineTo(c.x(), c.y() + hh)
|
|
498
514
|
path.lineTo(c.x() - hw, c.y())
|
|
499
515
|
path.closeSubpath()
|
|
516
|
+
else: # HEXAGON
|
|
517
|
+
c = rect.center()
|
|
518
|
+
hw = rect.width() / 2.0 + m
|
|
519
|
+
hh = rect.height() / 2.0 + m
|
|
520
|
+
path.moveTo(c.x() - hw / 2.0, c.y() - hh)
|
|
521
|
+
path.lineTo(c.x() + hw / 2.0, c.y() - hh)
|
|
522
|
+
path.lineTo(c.x() + hw, c.y())
|
|
523
|
+
path.lineTo(c.x() + hw / 2.0, c.y() + hh)
|
|
524
|
+
path.lineTo(c.x() - hw / 2.0, c.y() + hh)
|
|
525
|
+
path.lineTo(c.x() - hw, c.y())
|
|
526
|
+
path.closeSubpath()
|
|
500
527
|
return path
|
|
501
528
|
|
|
502
529
|
def _centerText(self) -> None:
|
|
@@ -78,6 +78,7 @@ class INodeItem(Protocol):
|
|
|
78
78
|
- ``ELLIPSE`` 椭圆/圆形(强制为宽高相等的标准圆)
|
|
79
79
|
- ``RECTANGLE`` 圆角矩形
|
|
80
80
|
- ``DIAMOND`` 菱形
|
|
81
|
+
- ``HEXAGON`` 六边形(平顶/平底, 左右尖角)
|
|
81
82
|
|
|
82
83
|
赋值后若未设置自定义半径, 会按新形状重算尺寸; 切换到圆形时
|
|
83
84
|
端口会从轴向排布切换为沿侧弧径向排布。
|
|
@@ -396,7 +397,8 @@ class INodeItem(Protocol):
|
|
|
396
397
|
"""计算从节点中心指向 ``target_pos`` 方向、落在节点外轮廓上的交点。
|
|
397
398
|
|
|
398
399
|
依据当前 :attr:`nodeShape` 采用不同算法: 圆形用三角函数、
|
|
399
|
-
圆角矩形先求外接矩形交点再修正圆角、菱形用 L1
|
|
400
|
+
圆角矩形先求外接矩形交点再修正圆角、菱形用 L1 范数缩放、
|
|
401
|
+
六边形取平顶边与斜边两组约束的较小射线交点。
|
|
400
402
|
|
|
401
403
|
Args:
|
|
402
404
|
target_pos: 目标位置(场景坐标)。
|
|
@@ -213,21 +213,22 @@ class _NodePortMixin:
|
|
|
213
213
|
# 端口布局
|
|
214
214
|
# ------------------------------------------------------------------ #
|
|
215
215
|
def _layoutPorts(self) -> None:
|
|
216
|
-
"""分布端口: 圆形沿侧弧径向;
|
|
216
|
+
"""分布端口: 圆形沿侧弧径向; 菱形/六边形沿左/右斜边贴合排布; 矩形沿边缘轴向。"""
|
|
217
217
|
is_circle = self._shape == NodeShape.ELLIPSE
|
|
218
|
-
|
|
218
|
+
# 菱形与六边形(左右均为两条斜边)共用斜边贴合布局
|
|
219
|
+
is_slant = self._shape in (NodeShape.DIAMOND, NodeShape.HEXAGON)
|
|
219
220
|
for side in ("left", "right"):
|
|
220
221
|
for it in self._port_items[side]:
|
|
221
|
-
# 互斥的排布模式: _radial(圆形) / _diamond(
|
|
222
|
+
# 互斥的排布模式: _radial(圆形) / _diamond(斜边) / 皆 False(矩形)
|
|
222
223
|
if it._radial != is_circle:
|
|
223
224
|
it._radial = is_circle
|
|
224
225
|
it.update()
|
|
225
|
-
if it._diamond !=
|
|
226
|
-
it._diamond =
|
|
226
|
+
if it._diamond != is_slant:
|
|
227
|
+
it._diamond = is_slant
|
|
227
228
|
it.update()
|
|
228
229
|
if is_circle:
|
|
229
230
|
self._layoutPortsOnCircle()
|
|
230
|
-
elif
|
|
231
|
+
elif is_slant:
|
|
231
232
|
self._layoutPortsOnDiamond()
|
|
232
233
|
else:
|
|
233
234
|
self._layoutPortsAxisAligned()
|
|
@@ -277,16 +278,29 @@ class _NodePortMixin:
|
|
|
277
278
|
it.setPos(QPointF(x, y))
|
|
278
279
|
it.update()
|
|
279
280
|
|
|
281
|
+
def _slantInsetX(self) -> float:
|
|
282
|
+
"""斜边上端点(顶/底侧)相对中心 x 的内缩量。
|
|
283
|
+
|
|
284
|
+
菱形: 0(斜边直达中线上的顶/底尖角);
|
|
285
|
+
六边形: half_w / 2(斜边从侧尖角连到平顶/平底边的端点)。
|
|
286
|
+
"""
|
|
287
|
+
if self._shape == NodeShape.HEXAGON:
|
|
288
|
+
return self.rect().width() / 4.0
|
|
289
|
+
return 0.0
|
|
290
|
+
|
|
280
291
|
def _layoutPortsOnDiamond(self) -> None:
|
|
281
|
-
"""
|
|
292
|
+
"""菱形/六边形: 端口沿左/右两条斜边贴合排布, dot 旋转使底边贴合斜边、
|
|
282
293
|
apex 沿斜边内法线(指向节点内部), 出射方向沿斜边外法线。
|
|
283
294
|
|
|
284
295
|
每侧(right/left)对应两条斜边(上、下), 总弧长 2L(L=单条斜边长)。
|
|
285
296
|
端口沿弧长以 PORT_MIN_GAP 紧凑排布, 整组围绕侧顶点(右顶点/左顶点,
|
|
286
297
|
即赤道)居中。**所有四个顶点(top/side/bottom 及对侧顶点)附近都预留
|
|
287
|
-
顶点间隙**, 故端口自然落在斜边中段,
|
|
298
|
+
顶点间隙**, 故端口自然落在斜边中段, 不压在任何尖角上。
|
|
299
|
+
|
|
300
|
+
斜边端点由 _slantInsetX() 参数化: 菱形斜边连到中线的顶/底尖角,
|
|
301
|
+
六边形斜边连到平顶/平底边的端点(x=±hw/2), 布局算法完全一致。
|
|
288
302
|
|
|
289
|
-
注意:
|
|
303
|
+
注意: 斜边的内法线是**该斜边的常量法线**(垂直于斜边), 而非"指向
|
|
290
304
|
圆心"的方向 —— 二者仅在斜边中点重合。dot 底边须平行于斜边, 故
|
|
291
305
|
_orient_angle 必须取斜边内法线方向(使旋转后底边贴合斜边)。
|
|
292
306
|
"""
|
|
@@ -296,20 +310,22 @@ class _NodePortMixin:
|
|
|
296
310
|
hh = rect.height() / 2.0
|
|
297
311
|
if hw <= 0 or hh <= 0:
|
|
298
312
|
return
|
|
299
|
-
|
|
300
|
-
#
|
|
301
|
-
|
|
302
|
-
#
|
|
303
|
-
#
|
|
304
|
-
#
|
|
313
|
+
kx = self._slantInsetX()
|
|
314
|
+
sw = hw - kx # 斜边的水平跨度(菱形=hw, 六边形=hw/2)
|
|
315
|
+
L = math.hypot(sw, hh) # 单条斜边长
|
|
316
|
+
# 斜边内法线(指向节点内部, 垂直于斜边)。四条斜边, 内法线方向固定。
|
|
317
|
+
# 右上(TR: top->right) 斜边方向 (sw, hh), 内法线 (-hh, sw)
|
|
318
|
+
# 右下(RB: right->bottom) 方向 (-sw, hh), 内法线 (-hh, -sw)
|
|
319
|
+
# 左下(BL: bottom->left) 方向 (-sw, -hh), 内法线 (hh, -sw)
|
|
320
|
+
# 左上(LT: left->top) 方向 (sw, -hh), 内法线 (hh, sw)
|
|
305
321
|
def inward_normal(ix, iy):
|
|
306
322
|
nl = math.hypot(ix, iy)
|
|
307
323
|
return (ix / nl, iy / nl) if nl > 1e-6 else (0.0, 0.0)
|
|
308
324
|
|
|
309
|
-
NORM_TR = inward_normal(-hh,
|
|
310
|
-
NORM_RB = inward_normal(-hh, -
|
|
311
|
-
NORM_BL = inward_normal(hh, -
|
|
312
|
-
NORM_LT = inward_normal(hh,
|
|
325
|
+
NORM_TR = inward_normal(-hh, sw) # 右上斜边内法线
|
|
326
|
+
NORM_RB = inward_normal(-hh, -sw) # 右下斜边内法线
|
|
327
|
+
NORM_BL = inward_normal(hh, -sw) # 左下斜边内法线
|
|
328
|
+
NORM_LT = inward_normal(hh, sw) # 左上斜边内法线
|
|
313
329
|
|
|
314
330
|
# 四个顶点处的弧长预留(像素, 沿弧长方向): 避免端口压在任何菱角上。
|
|
315
331
|
# top/bottom 顶点处一条斜边交汇, side 顶点处两条斜边交汇, 均需避让。
|
|
@@ -366,30 +382,31 @@ class _NodePortMixin:
|
|
|
366
382
|
s_values.append(L + vertex_gap + off_c) # 下半中段内
|
|
367
383
|
for it, s in zip(items, s_values):
|
|
368
384
|
# s ∈ [0, 2L]; 上半斜边 s∈[0,L](top→侧顶点), 下半 s∈[L,2L](侧顶点→bottom)
|
|
385
|
+
# 斜边端点: 侧尖角(±hw,0) <-> 顶/底端点(±kx,∓hh); kx=0 即菱形
|
|
369
386
|
if side == "right":
|
|
370
387
|
if s <= L:
|
|
371
|
-
# 上半(TR):
|
|
388
|
+
# 上半(TR): (kx,-hh) -> right(+hw,0), t=s/L
|
|
372
389
|
t = max(0.0, min(1.0, s / L))
|
|
373
|
-
px = cx +
|
|
390
|
+
px = cx + kx + sw * t
|
|
374
391
|
py = cy + (-hh) + hh * t
|
|
375
392
|
nx, ny = NORM_TR
|
|
376
393
|
else:
|
|
377
|
-
# 下半(RB): right(+hw,0) ->
|
|
394
|
+
# 下半(RB): right(+hw,0) -> (kx,+hh), t=(s-L)/L
|
|
378
395
|
t = max(0.0, min(1.0, (s - L) / L))
|
|
379
|
-
px = cx + hw
|
|
396
|
+
px = cx + hw - sw * t
|
|
380
397
|
py = cy + hh * t
|
|
381
398
|
nx, ny = NORM_RB
|
|
382
399
|
else: # left
|
|
383
400
|
if s <= L:
|
|
384
|
-
# 上半(LT):
|
|
401
|
+
# 上半(LT): (-kx,-hh) -> left(-hw,0), t=s/L
|
|
385
402
|
t = max(0.0, min(1.0, s / L))
|
|
386
|
-
px = cx
|
|
403
|
+
px = cx - kx - sw * t
|
|
387
404
|
py = cy + (-hh) + hh * t
|
|
388
405
|
nx, ny = NORM_LT
|
|
389
406
|
else:
|
|
390
|
-
# 下半(BL): left(-hw,0) ->
|
|
407
|
+
# 下半(BL): left(-hw,0) -> (-kx,+hh), t=(s-L)/L
|
|
391
408
|
t = max(0.0, min(1.0, (s - L) / L))
|
|
392
|
-
px = cx
|
|
409
|
+
px = cx - hw + sw * t
|
|
393
410
|
py = cy + hh * t
|
|
394
411
|
nx, ny = NORM_BL
|
|
395
412
|
it._orient_angle = math.degrees(math.atan2(ny, nx))
|
|
@@ -397,15 +414,15 @@ class _NodePortMixin:
|
|
|
397
414
|
it.update()
|
|
398
415
|
|
|
399
416
|
def _diamondSideBias(self) -> float:
|
|
400
|
-
"""
|
|
417
|
+
"""菱形/六边形每条斜边上端口组的"逻辑中点"参数(0=远端顶点, 1=侧顶点)。
|
|
401
418
|
|
|
402
419
|
取 0.7: 端口组偏向侧顶点(左右两角), 体现"左右两侧"的视觉聚类,
|
|
403
|
-
同时仍处于斜边中段(避开顶点预留区),
|
|
420
|
+
同时仍处于斜边中段(避开顶点预留区), 不会压在任何尖角上。
|
|
404
421
|
"""
|
|
405
422
|
return 0.7
|
|
406
423
|
|
|
407
424
|
def _diamondVertexGap(self) -> float:
|
|
408
|
-
"""
|
|
425
|
+
"""菱形/六边形每个顶点处沿弧长预留的间隙(像素)。
|
|
409
426
|
|
|
410
427
|
四个顶点(top/底/左/右)处斜边交汇, 法线方向不连续, 端口压在顶点上
|
|
411
428
|
视觉突兀。预留固定像素间隙使端口落在斜边中段。取 10px: 对常见尺寸
|
|
@@ -422,6 +439,8 @@ class _NodePortMixin:
|
|
|
422
439
|
return corner + 4.0
|
|
423
440
|
if self._shape == NodeShape.DIAMOND:
|
|
424
441
|
return half_h * 0.30
|
|
442
|
+
if self._shape == NodeShape.HEXAGON:
|
|
443
|
+
return half_h * 0.25
|
|
425
444
|
# ELLIPSE
|
|
426
445
|
return half_h * 0.22
|
|
427
446
|
|