Skip to content

Commit 92155f3

Browse files
committed
v1.5.2 - Slash commands, KiCad settings sync, improved auto-save
New Features: - Slash commands (/rev, /date, /company, etc.) with autocomplete popup - KiCad Settings Sync: autosave interval matches KiCad backup preferences - Three autosave modes: KiNotes (custom), KiCad sync, Disabled - Insert Variable submenu in Import dropdown - Centralized KiCad data extraction (kicad_extractor.py) - Improved Debug Info dialog with actionable hints Fixes: - Time tracking settings persistence (12hr/24hr, enable, show diary) - Auto-save timer interval persistence between sessions - PCM package structure (files directly under plugins/) - Import paths for PCM compatibility - Visual editor modified flag tracking Changes: - DEBUG_ENABLED=False for release - Updated metadata.json with v1.5.2
1 parent 36b8a90 commit 92155f3

File tree

11 files changed

+858
-147
lines changed

11 files changed

+858
-147
lines changed

KiNotes/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import os
99
import json
1010

11-
__version__ = "1.5.0"
11+
__version__ = "1.5.2"
1212
__author__ = "PCBtools.xyz"
1313
__license__ = "Apache-2.0"
1414

KiNotes/core/defaultsConfig.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
# DEBUG SETTINGS - Console output control
7878
# ============================================================
7979
DEBUG_ENABLED = False # Master debug flag - set True for development only
80-
DEPLOY_BUILD = 33 # Incremented by deploy script - verifies fresh deployment
80+
DEPLOY_BUILD = 66 # Incremented by deploy script - verifies fresh deployment
8181

8282
def debug_print(msg: str) -> None:
8383
"""Print debug message only if DEBUG_ENABLED is True."""
@@ -105,6 +105,19 @@ def debug_print(msg: str) -> None:
105105
'show_work_diary_button': True,
106106
}
107107

108+
# ============================================================
109+
# KICAD SETTINGS SYNC DEFAULTS
110+
# ============================================================
111+
KICAD_SYNC_DEFAULTS = {
112+
# Autosave mode: 'kinotes' (default 5s), 'kicad' (sync), 'disabled'
113+
'autosave_mode': 'kinotes',
114+
# KiNotes default interval when not syncing (ms)
115+
'kinotes_default_ms': 5000,
116+
# Minimum interval to prevent CPU overload (3s)
117+
'sync_min_interval_ms': 3000,
118+
# No max cap - matches KiCad exactly (even 15 min = 900000ms)
119+
}
120+
108121
# ============================================================
109122
# WINDOW SIZING DEFAULTS
110123
# ============================================================
@@ -314,7 +327,7 @@ def debug_print(msg: str) -> None:
314327
'md_import': True, # Markdown import (MD → RichText)
315328

316329
# UI modules
317-
'save': False, # Save operations
330+
'save': True, # Save operations (auto-save, manual save)
318331
'click': False, # Click events
319332
'size': False, # Window sizing
320333
'editor': False, # Visual editor operations
@@ -338,8 +351,12 @@ def debug_module(module: str, msg: str) -> None:
338351
msg: Message to print (without [KiNotes] prefix)
339352
"""
340353
if DEBUG_ENABLED and DEBUG_MODULES.get(module, False):
354+
import sys
341355
prefix = module.upper()
342-
print(f"[KiNotes {prefix}] {msg}")
356+
output = f"[KiNotes {prefix}] {msg}"
357+
# Use stderr for KiCad embedded Python (stdout may be redirected)
358+
print(output, file=sys.stderr)
359+
sys.stderr.flush()
343360

344361

345362
# ============================================================
@@ -366,6 +383,8 @@ def get_default_settings() -> dict:
366383
'blacklist_empty': DEFAULTS['blacklist_empty'],
367384
'background_color': DEFAULTS['bg_color_name'],
368385
'text_color': DEFAULTS['text_color_name'],
386+
# KiCad sync settings
387+
'autosave_mode': KICAD_SYNC_DEFAULTS['autosave_mode'],
369388
}
370389

371390

KiNotes/kinotes_action.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,18 @@ def _show_error_dialog(error_msg):
102102
def _force_reload_modules():
103103
"""Force reload all UI modules to pick up latest changes."""
104104
try:
105+
# Reload defaultsConfig FIRST (contains DEPLOY_BUILD and settings)
106+
from core import defaultsConfig
107+
importlib.reload(defaultsConfig)
108+
print("[KiNotes] Reloaded defaultsConfig")
109+
# Reload kicad_extractor (centralized extraction)
110+
from core import kicad_extractor
111+
importlib.reload(kicad_extractor)
112+
print("[KiNotes] Reloaded kicad_extractor")
113+
# Reload variable_snippets (/ command autocomplete)
114+
from core import variable_snippets
115+
importlib.reload(variable_snippets)
116+
print("[KiNotes] Reloaded variable_snippets")
105117
# Reload in dependency order: visual_editor, markdown_converter, then main_panel
106118
from ui import visual_editor, markdown_converter, main_panel
107119
importlib.reload(visual_editor)
@@ -115,6 +127,9 @@ def _force_reload_modules():
115127

116128
_force_reload_modules()
117129

130+
# Re-import DEPLOY_BUILD after reload to get fresh value
131+
from core.defaultsConfig import DEPLOY_BUILD
132+
118133
from core.notes_manager import NotesManager
119134
from core.designator_linker import DesignatorLinker
120135
from core.metadata_extractor import MetadataExtractor

KiNotes/metadata.json

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
{
22
"$schema": "https://go.kicad.org/pcm/schemas/v1",
33
"name": "KiNotes",
4-
"description": "Smart Engineering Notes for KiCad 9+ with @REF linking, BOM import, and PDF export",
5-
"description_full": "KiNotes brings real engineering notes directly inside KiCad pcbnew — with zero friction.\n\nFeatures:\n• Dual-Mode Editor: Visual WYSIWYG or Markdown\n• Auto-link designators: type @R1, @U3 → highlights component on PCB\nImport board metadata: BOM, Stackup, Netlist, Layers, Diff Pairs\n• Dark/Light themes with custom colors\nExport to PDF and Markdown\nPer-task time tracking with work diary\n• Git-friendly .kinotes/ folder storage\n\nBuilt by PCBtools.xyz for modern KiCad workflows.",
4+
"description": "Engineering notes that live inside KiCad — auto-save, Smart-Link, time tracking",
5+
"description_full": "Your design decisions shouldn't live in a separate notepad. KiNotes keeps engineering notes right inside KiCad—where they belong.\n\nA note written today saves hours tomorrow. A design decision documented now prevents the same argument six months from now.\n\nFeatures:\n• Smart-Link: Click R1, U3, C5 → highlights component on PCB\nNet highlighting: Click VCC, GND, SDA → highlights traces\nVisual WYSIWYG editor (Notion-like experience)\n• Auto-save: Your notes survive your forgetting\nDark/Light themes with custom colors\nTask list with time tracking\nExport to PDF\n• Git-friendly .kinotes/ folder storage\n• 100% offline, no cloud accounts\n\nBuilt for engineers who've learned that memory is unreliable, but good notes aren't.\n\nOpen source (Apache 2.0) by PCBtools.xyz",
66
"identifier": "com.pcbtools.kinotes",
77
"type": "plugin",
88
"author": {
99
"name": "PCBtools.xyz",
1010
"contact": {
11-
"web": "https://pcbtools.xyz",
12-
"github": "https://github.com/way2pramil"
11+
"web": "https://pcbtools.xyz"
1312
}
1413
},
1514
"maintainer": {
@@ -20,16 +19,14 @@
2019
},
2120
"license": "Apache-2.0",
2221
"resources": {
23-
"homepage": "https://pcbtools.xyz",
24-
"repository": "https://github.com/way2pramil/KiNotes",
25-
"issues": "https://github.com/way2pramil/KiNotes/issues"
22+
"homepage": "https://pcbtools.xyz/tools/kinotes",
23+
"repository": "https://github.com/way2pramil/KiNotes"
2624
},
2725
"versions": [
2826
{
29-
"version": "1.5.0",
30-
"date": "2025-12-17",
27+
"version": "1.5.2",
3128
"status": "stable",
3229
"kicad_version": "9.0"
33-
}
30+
}
3431
]
3532
}

KiNotes/ui/components/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
"""
22
KiNotes UI Components - Reusable widgets.
33
4-
Provides themed buttons, icons, and custom controls.
4+
Provides themed buttons, icons, autocomplete, and custom controls.
55
"""
66
from .buttons import RoundedButton, PlayPauseButton, ToggleSwitch
77
from .icons import Icons
8+
from .autocomplete_popup import SnippetAutocompletePopup
89

910
__all__ = [
1011
'RoundedButton',
1112
'PlayPauseButton',
1213
'ToggleSwitch',
13-
'Icons'
14+
'Icons',
15+
'SnippetAutocompletePopup'
1416
]

KiNotes/ui/dialogs/about_dialog.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@
1818

1919
# Import version from single source
2020
try:
21-
from ...__version__ import __version__
21+
from __version__ import __version__
2222
except ImportError:
23-
__version__ = "1.4.2" # Fallback
23+
try:
24+
from ...__version__ import __version__
25+
except ImportError:
26+
__version__ = "1.5.2" # Fallback
2427

2528
# Import debug_print
2629
try:

KiNotes/ui/dialogs/settings_dialog.py

Lines changed: 140 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,16 @@
3737
# Import centralized defaults - handle both KiCad plugin and standalone context
3838
try:
3939
from ...core.defaultsConfig import (
40-
WINDOW_DEFAULTS, PERFORMANCE_DEFAULTS, DEFAULTS, BETA_DEFAULTS, TIME_TRACKER_DEFAULTS, debug_print
40+
WINDOW_DEFAULTS, PERFORMANCE_DEFAULTS, DEFAULTS, BETA_DEFAULTS,
41+
TIME_TRACKER_DEFAULTS, KICAD_SYNC_DEFAULTS, debug_print
4142
)
43+
from ...core.kicad_extractor import get_kicad_sync
4244
except ImportError:
4345
from core.defaultsConfig import (
44-
WINDOW_DEFAULTS, PERFORMANCE_DEFAULTS, DEFAULTS, BETA_DEFAULTS, TIME_TRACKER_DEFAULTS, debug_print
46+
WINDOW_DEFAULTS, PERFORMANCE_DEFAULTS, DEFAULTS, BETA_DEFAULTS,
47+
TIME_TRACKER_DEFAULTS, KICAD_SYNC_DEFAULTS, debug_print
4548
)
49+
from core.kicad_extractor import get_kicad_sync
4650

4751

4852
# ------------------------------ Helpers ---------------------------------
@@ -521,7 +525,7 @@ def _build_panel_size_section(self, parent, sizer):
521525
self._build_pdf_format_section(self._scroll_panel, sizer)
522526

523527
def _build_performance_section(self, parent, sizer):
524-
"""Build performance settings section (timer interval)."""
528+
"""Build performance settings section (autosave mode + timer interval)."""
525529
perf_header = wx.StaticText(parent, label="⚡ Performance")
526530
set_label_style(perf_header, self._theme, bold=True, size=10)
527531
sizer.Add(perf_header, 0, wx.LEFT | wx.BOTTOM, SECTION_MARGIN)
@@ -535,34 +539,121 @@ def _build_performance_section(self, parent, sizer):
535539
current_settings = notes_manager.load_settings() if notes_manager else {}
536540
current_interval_ms = current_settings.get('timer_interval_ms', PERFORMANCE_DEFAULTS['timer_interval_ms'])
537541
current_interval_sec = current_interval_ms // 1000 # Convert to seconds for UI
538-
539-
# Timer interval row
540-
timer_row = wx.BoxSizer(wx.HORIZONTAL)
541-
542-
timer_label = wx.StaticText(perf_panel, label="Auto-save interval:")
543-
timer_label.SetForegroundColour(hex_to_colour(self._theme["text_primary"]))
544-
timer_row.Add(timer_label, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 8)
545-
546-
# SpinCtrl for interval (3-60 seconds)
542+
current_autosave_mode = current_settings.get('autosave_mode', KICAD_SYNC_DEFAULTS['autosave_mode'])
543+
544+
# ========== Autosave Mode Radio Buttons ==========
545+
mode_label = wx.StaticText(perf_panel, label="Auto-save mode:")
546+
mode_label.SetForegroundColour(hex_to_colour(self._theme["text_primary"]))
547+
perf_sizer.Add(mode_label, 0, wx.LEFT | wx.TOP, 10)
548+
549+
# Check KiCad sync availability and get all backup settings
550+
kicad_sync = get_kicad_sync()
551+
kicad_available = kicad_sync.is_available()
552+
kicad_interval_sec = None
553+
self._kicad_backup_settings = {} # Store for info panel
554+
if kicad_available:
555+
kicad_interval_ms = kicad_sync.get_autosave_interval_ms()
556+
if kicad_interval_ms:
557+
kicad_interval_sec = kicad_interval_ms // 1000
558+
# Get all backup settings for info display
559+
self._kicad_backup_settings = kicad_sync.get_all_backup_settings()
560+
561+
# ========== Row 1: KiNotes mode with inline spinner ==========
562+
kinotes_row = wx.BoxSizer(wx.HORIZONTAL)
563+
564+
self._autosave_mode_kinotes = wx.RadioButton(perf_panel, label=" KiNotes", style=wx.RB_GROUP)
565+
self._autosave_mode_kinotes.SetForegroundColour(hex_to_colour(self._theme["text_primary"]))
566+
self._autosave_mode_kinotes.SetValue(current_autosave_mode == 'kinotes')
567+
self._autosave_mode_kinotes.Bind(wx.EVT_RADIOBUTTON, self._on_autosave_mode_change)
568+
kinotes_row.Add(self._autosave_mode_kinotes, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 8)
569+
570+
# SpinCtrl inline with radio (3-300 seconds = 5 min max)
547571
min_sec = PERFORMANCE_DEFAULTS['timer_min_ms'] // 1000
548572
max_sec = PERFORMANCE_DEFAULTS['timer_max_ms'] // 1000
549573
self._timer_interval_spin = wx.SpinCtrl(perf_panel, min=min_sec, max=max_sec,
550-
initial=max(min_sec, min(current_interval_sec, max_sec)))
551-
block_scroll_wheel(self._timer_interval_spin) # Prevent accidental value changes while scrolling
574+
initial=max(min_sec, min(current_interval_sec, max_sec)),
575+
size=(70, -1))
576+
block_scroll_wheel(self._timer_interval_spin)
552577
self._timer_interval_spin.SetForegroundColour(hex_to_colour(self._theme["text_primary"]))
553578
self._timer_interval_spin.SetBackgroundColour(hex_to_colour(self._theme["bg_editor"]))
554-
timer_row.Add(self._timer_interval_spin, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 4)
579+
kinotes_row.Add(self._timer_interval_spin, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 4)
580+
581+
self._sec_label = wx.StaticText(perf_panel, label="seconds")
582+
self._sec_label.SetForegroundColour(hex_to_colour(self._theme["text_secondary"]))
583+
kinotes_row.Add(self._sec_label, 0, wx.ALIGN_CENTER_VERTICAL)
584+
585+
perf_sizer.Add(kinotes_row, 0, wx.LEFT | wx.TOP, 15)
586+
587+
# ========== Row 2: Sync with KiCad ==========
588+
if kicad_available and kicad_interval_sec:
589+
# Format interval for display (e.g., "5 min" or "30s")
590+
if kicad_interval_sec >= 60:
591+
mins = kicad_interval_sec // 60
592+
secs = kicad_interval_sec % 60
593+
if secs:
594+
interval_display = f"{mins}m {secs}s"
595+
else:
596+
interval_display = f"{mins} min"
597+
else:
598+
interval_display = f"{kicad_interval_sec}s"
599+
kicad_label = f" Sync with KiCad ({interval_display})"
600+
else:
601+
kicad_label = " Sync with KiCad (unavailable)"
602+
603+
self._autosave_mode_kicad = wx.RadioButton(perf_panel, label=kicad_label)
604+
self._autosave_mode_kicad.SetForegroundColour(hex_to_colour(self._theme["text_primary"]))
605+
self._autosave_mode_kicad.SetValue(current_autosave_mode == 'kicad')
606+
self._autosave_mode_kicad.Enable(kicad_available and kicad_interval_sec is not None)
607+
self._autosave_mode_kicad.Bind(wx.EVT_RADIOBUTTON, self._on_autosave_mode_change)
608+
perf_sizer.Add(self._autosave_mode_kicad, 0, wx.LEFT | wx.TOP, 15)
609+
610+
# ========== KiCad Info Panel (shown when KiCad sync selected) ==========
611+
self._kicad_info_panel = wx.Panel(perf_panel)
612+
self._kicad_info_panel.SetBackgroundColour(hex_to_colour(self._theme.get("bg_toolbar", "#F5F5F5")))
613+
kicad_info_sizer = wx.BoxSizer(wx.VERTICAL)
614+
615+
# Build info text from KiCad Session settings
616+
if self._kicad_backup_settings:
617+
session_display = self._kicad_backup_settings.get('session_autosave_display', 'N/A')
618+
backup_enabled = self._kicad_backup_settings.get('enabled', False)
619+
backup_on_autosave = self._kicad_backup_settings.get('backup_on_autosave', False)
620+
621+
info_lines = [
622+
f"KiCad → Preferences → Common → Session:",
623+
f" • Auto save: {session_display}",
624+
f"",
625+
f"Project Backup: {'✓ Enabled' if backup_enabled else '✗ Disabled'}",
626+
f" • Backup on autosave: {'✓' if backup_on_autosave else '✗'}",
627+
]
628+
info_text = "\n".join(info_lines)
629+
else:
630+
info_text = "KiCad settings not available"
631+
632+
kicad_info_label = wx.StaticText(self._kicad_info_panel, label=info_text)
633+
kicad_info_label.SetForegroundColour(hex_to_colour(self._theme.get("text_secondary", "#666666")))
634+
kicad_info_label.SetFont(wx.Font(9, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL))
635+
kicad_info_sizer.Add(kicad_info_label, 0, wx.ALL, 8)
555636

556-
sec_label = wx.StaticText(perf_panel, label="seconds")
557-
sec_label.SetForegroundColour(hex_to_colour(self._theme["text_secondary"]))
558-
timer_row.Add(sec_label, 0, wx.ALIGN_CENTER_VERTICAL)
637+
self._kicad_info_panel.SetSizer(kicad_info_sizer)
638+
perf_sizer.Add(self._kicad_info_panel, 0, wx.LEFT | wx.RIGHT | wx.TOP, 25)
559639

560-
perf_sizer.Add(timer_row, 0, wx.ALL, 10)
640+
# Show/hide based on current mode
641+
self._kicad_info_panel.Show(current_autosave_mode == 'kicad')
642+
643+
# ========== Row 3: Disabled ==========
644+
self._autosave_mode_disabled = wx.RadioButton(perf_panel, label=" Disabled (manual save only)")
645+
self._autosave_mode_disabled.SetForegroundColour(hex_to_colour(self._theme["text_primary"]))
646+
self._autosave_mode_disabled.SetValue(current_autosave_mode == 'disabled')
647+
self._autosave_mode_disabled.Bind(wx.EVT_RADIOBUTTON, self._on_autosave_mode_change)
648+
perf_sizer.Add(self._autosave_mode_disabled, 0, wx.LEFT | wx.TOP | wx.BOTTOM, 15)
649+
650+
# Enable/disable custom interval based on mode
651+
self._update_timer_controls_state()
561652

562653
perf_hint = wx.StaticText(perf_panel,
563-
label="Higher values = better performance, lower = faster saves (Min: 3s)")
654+
label="Higher values = better performance, lower = faster saves")
564655
perf_hint.SetForegroundColour(hex_to_colour(self._theme["text_secondary"]))
565-
perf_sizer.Add(perf_hint, 0, wx.LEFT | wx.BOTTOM, 10)
656+
perf_sizer.Add(perf_hint, 0, wx.LEFT | wx.TOP | wx.BOTTOM, 10)
566657

567658
perf_panel.SetSizer(perf_sizer)
568659
sizer.Add(perf_panel, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, SECTION_MARGIN)
@@ -571,6 +662,25 @@ def _build_performance_section(self, parent, sizer):
571662
self._add_separator(parent, sizer)
572663
sizer.AddSpacer(SECTION_SPACING)
573664

665+
def _on_autosave_mode_change(self, event):
666+
"""Handle autosave mode radio button change."""
667+
self._update_timer_controls_state()
668+
669+
# Show/hide KiCad info panel based on mode
670+
is_kicad_mode = self._autosave_mode_kicad.GetValue()
671+
if hasattr(self, '_kicad_info_panel'):
672+
self._kicad_info_panel.Show(is_kicad_mode)
673+
# Relayout the scroll panel
674+
self._scroll_panel.Layout()
675+
self._scroll_panel.FitInside()
676+
677+
def _update_timer_controls_state(self):
678+
"""Enable/disable custom interval controls based on autosave mode."""
679+
# Custom interval only enabled for 'kinotes' mode
680+
enabled = self._autosave_mode_kinotes.GetValue()
681+
self._timer_interval_spin.Enable(enabled)
682+
self._sec_label.Enable(enabled)
683+
574684
def _build_pdf_format_section(self, parent, sizer):
575685
"""Build PDF export format settings section."""
576686
pdf_header = wx.StaticText(parent, label="💾 PDF Export Format")
@@ -950,6 +1060,14 @@ def get_result(self):
9501060
bg_color_name = bg_choices[self._bg_choice.GetSelection()]
9511061
text_color_name = txt_choices[self._txt_choice.GetSelection()]
9521062

1063+
# Determine autosave mode from radio buttons
1064+
if self._autosave_mode_kicad.GetValue():
1065+
autosave_mode = 'kicad'
1066+
elif self._autosave_mode_disabled.GetValue():
1067+
autosave_mode = 'disabled'
1068+
else:
1069+
autosave_mode = 'kinotes'
1070+
9531071
return {
9541072
'dark_mode': self._selected_theme_dark,
9551073
'bg_color_name': bg_color_name if not self._selected_theme_dark else self._config.get('bg_color_name', 'Ivory Paper'),
@@ -967,6 +1085,7 @@ def get_result(self):
9671085
'scale_factor': None if self._scale_auto_checkbox.GetValue() else self._scale_slider.GetValue() / 100.0,
9681086
'panel_width': self._panel_width_spin.GetValue(),
9691087
'panel_height': self._panel_height_spin.GetValue(),
1088+
'autosave_mode': autosave_mode,
9701089
'timer_interval_ms': self._timer_interval_spin.GetValue() * 1000, # Convert seconds to ms
9711090
'beta_markdown': self._beta_markdown_cb.GetValue(),
9721091
'beta_bom': self._beta_bom_cb.GetValue(),

0 commit comments

Comments
 (0)