jwwkit 0.2.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.
- jwwkit/__init__.py +14 -0
- jwwkit/__main__.py +4 -0
- jwwkit/_commands/__init__.py +1 -0
- jwwkit/_commands/_integrity.py +55 -0
- jwwkit/_commands/check_installation.py +484 -0
- jwwkit/_commands/compare_drawings.py +29 -0
- jwwkit/_commands/inspect_jwf.py +24 -0
- jwwkit/_commands/recognize_planar_tategu.py +20 -0
- jwwkit/_commands/summarize_drawing.py +16 -0
- jwwkit/_comparison_composites.py +179 -0
- jwwkit/_edit_result_core.py +175 -0
- jwwkit/_ezjww_adapter.py +12 -0
- jwwkit/_geometry_engine.py +204 -0
- jwwkit/_geometry_math.py +93 -0
- jwwkit/_normalization_inspection.py +83 -0
- jwwkit/_replacement_contract.py +161 -0
- jwwkit/_resources.py +23 -0
- jwwkit/_spatial_distance.py +95 -0
- jwwkit/_spatial_query.py +34 -0
- jwwkit/_tategu_features.py +201 -0
- jwwkit/_tategu_geometry.py +108 -0
- jwwkit/alignment.py +85 -0
- jwwkit/cad_model.py +5 -0
- jwwkit/cli.py +40 -0
- jwwkit/drawing_comparison.py +430 -0
- jwwkit/edit_result.py +136 -0
- jwwkit/geometry_view.py +141 -0
- jwwkit/guides/AGENTS.md +47 -0
- jwwkit/guides/docs/api_list.md +1944 -0
- jwwkit/guides/docs/common_edit_result_coverage.md +40 -0
- jwwkit/guides/docs/drawing_comparison.md +79 -0
- jwwkit/guides/docs/edit_results.md +59 -0
- jwwkit/guides/docs/geometry_view.md +64 -0
- jwwkit/guides/docs/jwf_key_catalog.md +244 -0
- jwwkit/guides/docs/jwf_readonly.md +76 -0
- jwwkit/guides/docs/plan_api_coverage.md +75 -0
- jwwkit/guides/docs/read_view.md +89 -0
- jwwkit/guides/docs/reading_to_editing.md +100 -0
- jwwkit/guides/docs/schema_normalization.md +64 -0
- jwwkit/guides/docs/search_queries.md +95 -0
- jwwkit/guides/docs/spatial_search.md +77 -0
- jwwkit/guides/docs/tategu_recognition.md +40 -0
- jwwkit/guides/docs/text_equivalence.md +42 -0
- jwwkit/guides/docs/usage_guide.md +604 -0
- jwwkit/guides/docs/workflow_settings.md +95 -0
- jwwkit/hatch_geometry.py +77 -0
- jwwkit/jwf.py +202 -0
- jwwkit/legacy_document.py +2 -0
- jwwkit/line_document.py +14 -0
- jwwkit/model/__init__.py +1 -0
- jwwkit/model/base.py +15 -0
- jwwkit/model/block_definition.py +32 -0
- jwwkit/model/block_names.py +41 -0
- jwwkit/model/block_operations.py +154 -0
- jwwkit/model/block_reference.py +39 -0
- jwwkit/model/circle.py +44 -0
- jwwkit/model/circular_solid.py +72 -0
- jwwkit/model/common_entities.py +34 -0
- jwwkit/model/dimension.py +49 -0
- jwwkit/model/display_state.py +46 -0
- jwwkit/model/document.py +585 -0
- jwwkit/model/editing.py +270 -0
- jwwkit/model/group_audit.py +91 -0
- jwwkit/model/group_import.py +249 -0
- jwwkit/model/line.py +31 -0
- jwwkit/model/point.py +41 -0
- jwwkit/model/preflight.py +120 -0
- jwwkit/model/preset_rigid.py +42 -0
- jwwkit/model/query.py +270 -0
- jwwkit/model/query_fields.py +189 -0
- jwwkit/model/query_regex_worker.py +20 -0
- jwwkit/model/query_text.py +116 -0
- jwwkit/model/rigid_audit.py +66 -0
- jwwkit/model/solid.py +51 -0
- jwwkit/model/spatial.py +99 -0
- jwwkit/model/summary.py +93 -0
- jwwkit/model/text.py +49 -0
- jwwkit/model/text_metrics.py +78 -0
- jwwkit/model/transformations.py +176 -0
- jwwkit/normalization.py +287 -0
- jwwkit/read_view.py +298 -0
- jwwkit/reader/__init__.py +1 -0
- jwwkit/reader/archive.py +299 -0
- jwwkit/reader/images.py +48 -0
- jwwkit/reader/jws.py +175 -0
- jwwkit/reader/jws_placement.py +78 -0
- jwwkit/reader/sxf.py +51 -0
- jwwkit/reader/template_names.py +22 -0
- jwwkit/reader/text_presets.py +23 -0
- jwwkit/record_reader.py +2 -0
- jwwkit/replacement_workflow.py +39 -0
- jwwkit/resources/README.md +7 -0
- jwwkit/resources/empty_template.jww +0 -0
- jwwkit/resources/halfwidth_kana_composition_v1.json +35 -0
- jwwkit/resources/header.json +3 -0
- jwwkit/resources/jwf_schema.json +925 -0
- jwwkit/resources/jww_workflow.example.toml +14 -0
- jwwkit/resources/text_equivalence_v1.json +1779 -0
- jwwkit/safe_edit.py +454 -0
- jwwkit/spatial_search.py +144 -0
- jwwkit/tategu_recognition.py +181 -0
- jwwkit/workflow_settings.py +140 -0
- jwwkit/writer/__init__.py +1 -0
- jwwkit/writer/header.py +104 -0
- jwwkit/writer/legacy_document.py +62 -0
- jwwkit/writer/mfc.py +110 -0
- jwwkit/writer/preserved.py +265 -0
- jwwkit/writer/strings.py +19 -0
- jwwkit/writer/template_constants.py +9 -0
- jwwkit/writer/template_document.py +390 -0
- jwwkit-0.2.0.dist-info/METADATA +174 -0
- jwwkit-0.2.0.dist-info/RECORD +117 -0
- jwwkit-0.2.0.dist-info/WHEEL +5 -0
- jwwkit-0.2.0.dist-info/entry_points.txt +2 -0
- jwwkit-0.2.0.dist-info/licenses/LICENSE +21 -0
- jwwkit-0.2.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +51 -0
- jwwkit-0.2.0.dist-info/top_level.txt +1 -0
jwwkit/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Read, inspect and safely edit Jw_cad drawings."""
|
|
2
|
+
from ._resources import guide_paths, template_path
|
|
3
|
+
from .cad_model import CadBlockDefinition, CadDocument, CadEntity, JwsDocument
|
|
4
|
+
from .jwf import JwfDocument, JwfSetting, jwf_setting_catalog
|
|
5
|
+
from .legacy_document import LegacyDocument
|
|
6
|
+
from .line_document import (
|
|
7
|
+
BlockDefinition, BlockReference, Circle, Dimension, JwwDocument, Line, Point, Solid, Text,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"BlockDefinition", "BlockReference", "CadBlockDefinition", "CadDocument", "CadEntity",
|
|
12
|
+
"Circle", "Dimension", "JwfDocument", "JwfSetting", "JwsDocument", "JwwDocument",
|
|
13
|
+
"LegacyDocument", "Line", "Point", "Solid", "Text", "guide_paths", "jwf_setting_catalog", "template_path",
|
|
14
|
+
]
|
jwwkit/__main__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Command implementations for jwwkit."""
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Offline checks for package data and the installed wheel's RECORD."""
|
|
2
|
+
import base64
|
|
3
|
+
import hashlib
|
|
4
|
+
from importlib import metadata
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from jwwkit import template_path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
_TEMPLATE_SHA256 = "0913d5f6755b7038c11cda8be75c2502511ce85a007f9bcc2f10df049f6eff1e"
|
|
11
|
+
_DRAWINGS = {".jww", ".jws", ".jwc", ".jwk", ".jwf", ".dxf", ".pdf"}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def check_installed_files() -> int:
|
|
15
|
+
template = template_path()
|
|
16
|
+
package_root = template.parent.parent
|
|
17
|
+
if hashlib.sha256(template.read_bytes()).hexdigest() != _TEMPLATE_SHA256:
|
|
18
|
+
raise ValueError("Installed empty template hash mismatch")
|
|
19
|
+
|
|
20
|
+
files = set()
|
|
21
|
+
for path in package_root.rglob("*"):
|
|
22
|
+
if "__pycache__" in path.parts:
|
|
23
|
+
continue
|
|
24
|
+
if path.is_symlink() or getattr(path.stat(), "st_file_attributes", 0) & 0x400:
|
|
25
|
+
raise ValueError(f"Link or reparse point in jwwkit: {path}")
|
|
26
|
+
if not path.is_file():
|
|
27
|
+
continue
|
|
28
|
+
if path.name.casefold() in {"desktop.ini", "thumbs.db"} or path.suffix == ".pyc":
|
|
29
|
+
continue
|
|
30
|
+
if path.name.casefold() == "ai_settings_summary.json":
|
|
31
|
+
raise ValueError(f"Personal JWF summary in jwwkit: {path}")
|
|
32
|
+
if path.suffix.casefold() in _DRAWINGS and path != template:
|
|
33
|
+
raise ValueError(f"Unexpected drawing in jwwkit: {path}")
|
|
34
|
+
files.add(path)
|
|
35
|
+
|
|
36
|
+
distribution = metadata.distribution("jwwkit")
|
|
37
|
+
records = {
|
|
38
|
+
Path(item.locate()): item
|
|
39
|
+
for item in distribution.files or ()
|
|
40
|
+
if item.parts[0] == "jwwkit" and "__pycache__" not in item.parts
|
|
41
|
+
}
|
|
42
|
+
# Editable installations record the path hook and metadata, not package files.
|
|
43
|
+
if records:
|
|
44
|
+
if set(records) != files:
|
|
45
|
+
extra = sorted(str(path.relative_to(package_root)) for path in files - records.keys())
|
|
46
|
+
missing = sorted(str(path.relative_to(package_root)) for path in records.keys() - files)
|
|
47
|
+
raise ValueError(f"Unexpected or missing installed jwwkit files: extra={extra}, missing={missing}")
|
|
48
|
+
for path, record in records.items():
|
|
49
|
+
data = path.read_bytes()
|
|
50
|
+
if record.hash is None or record.size != len(data):
|
|
51
|
+
raise ValueError(f"Installed jwwkit file size or RECORD mismatch: {path.name}")
|
|
52
|
+
digest = hashlib.new(record.hash.mode, data).digest()
|
|
53
|
+
if base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii") != record.hash.value:
|
|
54
|
+
raise ValueError(f"Installed jwwkit file hash mismatch: {path.name}")
|
|
55
|
+
return len(files)
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
"""Offline distribution integrity and executable guide checks (synthetic data only)."""
|
|
2
|
+
import contextlib
|
|
3
|
+
import hashlib
|
|
4
|
+
import io
|
|
5
|
+
import json
|
|
6
|
+
import math
|
|
7
|
+
import platform
|
|
8
|
+
from importlib.resources import files
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import re
|
|
11
|
+
import struct
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
import tempfile
|
|
15
|
+
from unittest.mock import patch
|
|
16
|
+
|
|
17
|
+
import jwwkit
|
|
18
|
+
from jwwkit import guide_paths, template_path
|
|
19
|
+
from jwwkit._commands._integrity import check_installed_files
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _run_tool(command, args, cwd):
|
|
23
|
+
return subprocess.run(
|
|
24
|
+
[sys.executable, "-I", "-B", "-X", "utf8", "-m", "jwwkit", command, *args],
|
|
25
|
+
cwd=cwd, capture_output=True, text=True, encoding="utf-8",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def check(*, compare_command, inspect_jwf_command, summarize_command):
|
|
30
|
+
if sys.flags.optimize:
|
|
31
|
+
raise RuntimeError("Run without -O: executable guide assertions must remain enabled")
|
|
32
|
+
count = check_installed_files()
|
|
33
|
+
guides = guide_paths()["usage_guide"].parent
|
|
34
|
+
template_file = template_path()
|
|
35
|
+
from jwwkit.model.query_text import equivalent_text,normalization_options,_kana_composition
|
|
36
|
+
width_options=normalization_options({'width':True},'exact')
|
|
37
|
+
for original,expected in _kana_composition().items():
|
|
38
|
+
assert equivalent_text(original,width_options)==expected
|
|
39
|
+
assert equivalent_text('A ガ パ ヴ ① カ\u3099',width_options)=='A ガ パ ヴ ① カ\u3099'
|
|
40
|
+
from jwwkit.cad_model import CadDocument, JwsDocument
|
|
41
|
+
from jwwkit.line_document import JwwDocument, Line, Text, Circle
|
|
42
|
+
from jwwkit.writer.mfc import records_bytes
|
|
43
|
+
from jwwkit.reader.jws import JWS700_RESERVED
|
|
44
|
+
text = (guides/'usage_guide.md').read_text(encoding='utf-8')
|
|
45
|
+
blocks = re.findall(r'```python\n(.*?)\n```', text, re.S)
|
|
46
|
+
if len(blocks) != 13:
|
|
47
|
+
raise ValueError('Guide code inventory changed; update executable checks')
|
|
48
|
+
ns = {'__name__': 'distribution_guide'}
|
|
49
|
+
results = []
|
|
50
|
+
with tempfile.TemporaryDirectory(prefix='jww-install-check-') as tmp, \
|
|
51
|
+
patch('builtins.input', side_effect=AssertionError('No background keyboard approval')), \
|
|
52
|
+
contextlib.redirect_stdout(io.StringIO()):
|
|
53
|
+
for i, code in enumerate(blocks):
|
|
54
|
+
with contextlib.chdir(tmp):
|
|
55
|
+
exec(compile(code, f'usage_guide.md:block{i+1}', 'exec'), ns)
|
|
56
|
+
folder = Path(tmp)
|
|
57
|
+
source = folder/'input.jww'
|
|
58
|
+
new = JwwDocument()
|
|
59
|
+
new.set_group_scale(0, 50); new.set_group_scale(1, 25)
|
|
60
|
+
new.add_text((10,10), content='置換前')
|
|
61
|
+
new.add_line((2,2),(4,2),layer=1)
|
|
62
|
+
new.add_line((10,2),(12,2),layer=2)
|
|
63
|
+
new.add_line((160,120),(180,120),layer=4,layer_group=1)
|
|
64
|
+
new.save(source)
|
|
65
|
+
original = source.read_bytes()
|
|
66
|
+
cad = CadDocument.open(source)
|
|
67
|
+
if cad.diagnostics or len(cad.entities) != 4:
|
|
68
|
+
raise AssertionError('Synthetic template/reader smoke failure')
|
|
69
|
+
report = ns['read_report'](source,folder/'summary.json')
|
|
70
|
+
assert report['entity_count']==4 and report['texts'][0]['content']=='置換前'
|
|
71
|
+
assert sum(l['count'] for g in report['layer_groups'] for l in g['layers'])==4
|
|
72
|
+
assert cad.convert_point((1000,0),layer_group=0,from_units='real',to_units='paper')==(20.,0.)
|
|
73
|
+
assert cad.convert_point((1000,0),layer_group=1,from_units='real',to_units='paper')==(40.,0.)
|
|
74
|
+
results.append('summary_and_group_units')
|
|
75
|
+
before=ns['save_group_snapshot'](source,1,folder/'group_snapshot.json')
|
|
76
|
+
assert ns['check_group_snapshot'](source,1,folder/'group_snapshot.json')['passed']
|
|
77
|
+
changed=CadDocument.open(source);changed.set_group_name(1,'AUDIT_CHANGED');changed.save(folder/'group_changed.jww')
|
|
78
|
+
assert not ns['check_group_snapshot'](folder/'group_changed.jww',1,folder/'group_snapshot.json')['passed']
|
|
79
|
+
results.append('strict_group_snapshot_persist_and_difference')
|
|
80
|
+
receipt = ns['plan_edit'](source,folder/'edited.jww',folder/'edit.plan.json')
|
|
81
|
+
assert not (folder/'edited.jww').exists() and source.read_bytes()==original
|
|
82
|
+
# Explicit synthetic test approval, never approval for a user's drawing.
|
|
83
|
+
report = ns['run_approved'](receipt['path'], receipt['sha256'])
|
|
84
|
+
assert (report['modified_count'],report['deleted_count'],report['added_count'])==(2,1,0)
|
|
85
|
+
saved = CadDocument.open(folder/'edited.jww')
|
|
86
|
+
assert saved.entities[0].geometry['content']=='置換後'
|
|
87
|
+
assert saved.line_points(1,units='real')==((200.,100.),(300.,100.))
|
|
88
|
+
assert ns['view'](saved.entities[-1])==ns['view'](cad.entities[-1])
|
|
89
|
+
results.append('plan_edit_reload_and_non_target')
|
|
90
|
+
# Build a documented-schema JWS from synthetic LINE records, not any sample.
|
|
91
|
+
header = b'JwsData.'+b'.'*192+struct.pack('<I2d16d',700,0,0,*([50.]*16))+JWS700_RESERVED+struct.pack('<4d',0,0,4,4)
|
|
92
|
+
registry = dict(classes={},next_index=1)
|
|
93
|
+
data = header+records_bytes([Line((0,0),(4,0),layer=3).record(),Line((4,0),(4,4),layer=5,color=4).record()],registry)
|
|
94
|
+
data += records_bytes([],registry)
|
|
95
|
+
figure = folder/'symbol.jws'; figure.write_bytes(data)
|
|
96
|
+
assert JwsDocument.open(figure).to_bytes()==data
|
|
97
|
+
receipt = ns['plan_symbol'](source,figure,folder/'symbols.jww',folder/'symbol.plan.json')
|
|
98
|
+
report = ns['run_approved'](receipt['path'],receipt['sha256'])
|
|
99
|
+
symbols = CadDocument.open(folder/'symbols.jww')
|
|
100
|
+
assert symbols.block_reference_counts()=={'SYMBOL':2} and report['added_count']==2
|
|
101
|
+
assert symbols.entities[-1].geometry['scale_y']<0
|
|
102
|
+
assert symbols.entities[-1].geometry['scale_x']==1.5
|
|
103
|
+
assert figure.read_bytes()==data
|
|
104
|
+
results.append('jws_place_copy_rotate_reflect_scale')
|
|
105
|
+
groupsource = folder/'groups.jww'
|
|
106
|
+
new = JwwDocument();new.set_group_scale(0,50)
|
|
107
|
+
for x in (0,20):
|
|
108
|
+
new.add_line((x,0),(x+4,0),layer=3,color=2,width=2)
|
|
109
|
+
new.add_line((x+4,0),(x+4,4),layer=5,color=4,linetype=2,width=3)
|
|
110
|
+
new.add_line((80,60),(90,60),layer=4);new.save(groupsource)
|
|
111
|
+
before = CadDocument.open(groupsource)
|
|
112
|
+
receipt = ns['plan_groups'](groupsource,folder/'blocks.jww',folder/'groups.plan.json')
|
|
113
|
+
report = ns['run_approved'](receipt['path'],receipt['sha256'])
|
|
114
|
+
saved = CadDocument.open(folder/'blocks.jww')
|
|
115
|
+
assert saved.block_reference_counts()=={'MARK':2} and len(saved.block_definitions)==1
|
|
116
|
+
assert [saved.get_block_layer_priority(i) for i in (0,1)]==[True,False]
|
|
117
|
+
assert ns['view'](saved.entities[-1])==ns['view'](before.entities[-1])
|
|
118
|
+
assert [e.attributes for e in saved.block_definitions[0].entities]==[e.attributes for e in before.entities[:2]]
|
|
119
|
+
results.append('block_reuse_priority_mixed_attributes')
|
|
120
|
+
rejection = ns['report_plan_refusal'](source,folder/'refuse.plan.json',folder/'refused.jww')
|
|
121
|
+
assert rejection['reason']=='Mirrored glyphs cannot be represented by verified text fields'
|
|
122
|
+
assert not rejection['saved'] and source.read_bytes()==original
|
|
123
|
+
results.append('refusal_without_source_change')
|
|
124
|
+
receipt = ns['plan_edit'](source,folder/'stale.jww',folder/'stale.plan.json')
|
|
125
|
+
source.write_bytes(original+b'changed')
|
|
126
|
+
try:
|
|
127
|
+
ns['run_approved'](receipt['path'],receipt['sha256'])
|
|
128
|
+
except ValueError:
|
|
129
|
+
pass
|
|
130
|
+
else:
|
|
131
|
+
raise AssertionError('Changed source was accepted')
|
|
132
|
+
assert not (folder/'stale.jww').exists();source.write_bytes(original)
|
|
133
|
+
results.append('stale_plan_refused')
|
|
134
|
+
receipt = ns['plan_add_geometry'](source,folder/'add_geometry.jww',folder/'add_geometry.plan.json')
|
|
135
|
+
assert not (folder/'add_geometry.jww').exists()
|
|
136
|
+
report = ns['run_approved'](receipt['path'],receipt['sha256'])
|
|
137
|
+
assert report['added_count']==8 and report['modified_count']==0
|
|
138
|
+
added = CadDocument.open(folder/'add_geometry.jww')
|
|
139
|
+
assert [e.entity_type for e in added.entities[-8:]]==['LINE','TEXT','CIRCLE','ARC','POINT','SOLID','CIRCLE_SOLID','CIRCLE_SOLID']
|
|
140
|
+
assert added.entities[-3].attributes['pen_color']==102 and added.entities[-3].attributes['pen_style']==31
|
|
141
|
+
assert added.entities[-2].attributes['pen_style']==105
|
|
142
|
+
assert added.entities[-2].geometry['solid_mode']*added.get_group_scale(0)==50
|
|
143
|
+
filled=added.entities[-1]
|
|
144
|
+
assert (filled.attributes['layer_group'],filled.attributes['layer'])==(1,7)
|
|
145
|
+
assert filled.geometry['center_x']*added.get_group_scale(1)==2600
|
|
146
|
+
assert filled.geometry['radius']*added.get_group_scale(1)==100
|
|
147
|
+
results.append('eight_geometry_constructor_plans')
|
|
148
|
+
incoming=folder/'incoming.jww';incoming.write_bytes(source.read_bytes())
|
|
149
|
+
receipt=ns['plan_import_group'](source,incoming,folder/'imported.jww',folder/'import.plan.json',source_group=0,target_group=3)
|
|
150
|
+
assert not (folder/'imported.jww').exists()
|
|
151
|
+
report=ns['run_approved'](receipt['path'],receipt['sha256'])
|
|
152
|
+
assert report['added_count']==3 and report['modified_count']==0 and report['deleted_count']==0
|
|
153
|
+
imported=CadDocument.open(folder/'imported.jww')
|
|
154
|
+
assert imported.line_points(5,units='real')==CadDocument.open(source).line_points(1,units='real')
|
|
155
|
+
results.append('import_group_dependency_plan_and_real_coordinates')
|
|
156
|
+
|
|
157
|
+
dimsource = folder/'dimension.jww';new = JwwDocument()
|
|
158
|
+
new.add_dimension(Line((0,0),(10,0)),Text((2,1),(5,1),'10'))
|
|
159
|
+
new.add_line((1,2),(3,2),layer_group=1);new.save(dimsource)
|
|
160
|
+
receipt = ns['plan_names_and_dimension'](dimsource,folder/'names.jww',folder/'names.plan.json')
|
|
161
|
+
assert receipt['plan']['schema_version']==2 and len(receipt['plan']['resource_targets'])==3
|
|
162
|
+
report = ns['run_approved'](receipt['path'],receipt['sha256'])
|
|
163
|
+
named = CadDocument.open(folder/'names.jww')
|
|
164
|
+
assert named.entities[0].geometry['text']['content']=='寸法表示'
|
|
165
|
+
assert named.get_group_scale(1)==100 and named.get_layer_name(0,1)=='線'
|
|
166
|
+
assert named.get_group_name(1)=='配置用' and report['changed_group_count']==2
|
|
167
|
+
assert named.line_points(1,units='real')==((100.,200.),(300.,200.))
|
|
168
|
+
results.append('dimension_names_scales_and_resource_review')
|
|
169
|
+
states=ns['read_display_states'](source)
|
|
170
|
+
assert states['group']['state']=='editable'
|
|
171
|
+
receipt=ns['plan_display_states'](source,folder/'states.jww',folder/'states.plan.json')
|
|
172
|
+
assert not (folder/'states.jww').exists()
|
|
173
|
+
report=ns['run_approved'](receipt['path'],receipt['sha256'])
|
|
174
|
+
states=ns['read_display_states'](folder/'states.jww')
|
|
175
|
+
assert states['group']['state']=='display_only' and states['layer']['state']=='hidden'
|
|
176
|
+
assert report['changed_entity_count']==0 and report['changed_group_count']==1
|
|
177
|
+
results.append('display_states_read_plan_approval_reload')
|
|
178
|
+
receipt = ns['plan_definition'](source,folder/'new_definition.jww',folder/'definition.plan.json')
|
|
179
|
+
report = ns['run_approved'](receipt['path'],receipt['sha256'])
|
|
180
|
+
defined = CadDocument.open(folder/'new_definition.jww')
|
|
181
|
+
assert report['added_count']==2 and defined.block_reference_counts()=={'CREATED':2}
|
|
182
|
+
assert defined.get_block_layer_priority(4) and not defined.get_block_layer_priority(5)
|
|
183
|
+
assert defined.block_definitions[0].entities[0].attributes['flag']==0
|
|
184
|
+
assert [e.attributes['layer'] for e in defined.block_definitions[0].entities]==[3,5]
|
|
185
|
+
assert defined.entities[-2].geometry['ref_x']==20 and defined.entities[-1].geometry['ref_x']==80
|
|
186
|
+
receipt = ns['plan_existing_definition'](folder/'new_definition.jww',defined.block_definitions[0].number,
|
|
187
|
+
folder/'more_refs.jww',folder/'more_refs.plan.json')
|
|
188
|
+
report = ns['run_approved'](receipt['path'],receipt['sha256'])
|
|
189
|
+
assert report['added_count']==1 and not report['definitions_changed']
|
|
190
|
+
assert CadDocument.open(folder/'more_refs.jww').block_reference_counts()=={'CREATED':3}
|
|
191
|
+
results.append('new_definition_alias_priority_and_existing_placement')
|
|
192
|
+
receipt=ns['plan_alignment'](source,[1],[[100,100],[200,100]],[[300,400],[300,500]],
|
|
193
|
+
folder/'aligned.jww',folder/'align.plan.json',distance_tolerance=0,angle_tolerance=90,angle_units='deg')
|
|
194
|
+
review=receipt['plan']['resource_targets'][0]['alignment']
|
|
195
|
+
assert review['angle_input_units']=='deg' and review['angle_difference_deg']==90
|
|
196
|
+
report=ns['run_approved'](receipt['path'],receipt['sha256'])
|
|
197
|
+
assert report['modified_count']==1 and not report['groups_changed']
|
|
198
|
+
assert CadDocument.open(folder/'aligned.jww').line_points(1,units='real')==((300.,400.),(300.,500.))
|
|
199
|
+
results.append('two_point_alignment_real_degrees_and_approval')
|
|
200
|
+
# Exercise the newly distributed workflow using ONLY synthetic files.
|
|
201
|
+
building_paths=[]
|
|
202
|
+
for name in ('current_building','new_building'):
|
|
203
|
+
d=CadDocument.open(template_file)
|
|
204
|
+
for g in (1,2,3):d.set_group_scale(g,100)
|
|
205
|
+
d.set_group_scale(8,50)
|
|
206
|
+
d.add_line((0,0),(1000,100 if name=='new_building' else 0),layer_group=1,units='real')
|
|
207
|
+
d.add_line((20,20),(30,30),layer_group=2,units='real')
|
|
208
|
+
for point in ((0,0),(1000,0)):d.add_point(point,layer_group=3,layer=0,units='real',is_temporary=True)
|
|
209
|
+
path=folder/(name+'.jww');d.save(path);building_paths.append(path)
|
|
210
|
+
current,new_building=building_paths;original_buildings=[p.read_bytes() for p in building_paths]
|
|
211
|
+
refs=ns['replacement_reference_points'](current)
|
|
212
|
+
assert [row['index'] for row in refs]==[2,3] and refs[1]['real_mm']==(1000.,0.)
|
|
213
|
+
replacement=ns['plan_building_replacement'](current,new_building,folder/'replacement.jww',folder/'replacement.plan.json',
|
|
214
|
+
archive_group=8,building_group=1,source_reference_indices=[2,3],target_reference_indices=[2,3],
|
|
215
|
+
distance_tolerance=0,angle_tolerance=0,settings_policy='destination')
|
|
216
|
+
assert not (folder/'replacement.jww').exists()
|
|
217
|
+
assert replacement['plan']['actions'][0]['op']=='set_group_scale'
|
|
218
|
+
assert next(row for row in replacement['plan']['resource_targets'] if 'settings' in row)['settings']['settings_policy']=='destination'
|
|
219
|
+
report=ns['run_approved'](replacement['path'],replacement['sha256'])
|
|
220
|
+
assert (report['modified_count'],report['added_count'],report['deleted_count'])==(1,1,0)
|
|
221
|
+
audit=ns['check_replacement_groups'](folder/'replacement.jww',Path(replacement['path']).with_suffix('.groups.json'))
|
|
222
|
+
assert audit['passed'] and audit['archive_count']==1 and audit['archive_state']['state']=='hidden'
|
|
223
|
+
assert CadDocument.open(folder/'replacement.jww').get_group_scale(8)==100
|
|
224
|
+
assert [p.read_bytes() for p in building_paths]==original_buildings
|
|
225
|
+
results.append('building_replacement_destination_policy_and_complete_group_audits')
|
|
226
|
+
for case,angle in (('translation',0.),('rotation',math.radians(5))):
|
|
227
|
+
displaced=CadDocument.open(new_building)
|
|
228
|
+
displaced.transform_entities([0,2,3],translation=(1000,-500),rotation=angle,units='real')
|
|
229
|
+
path=folder/(case+'_source.jww');displaced.save(path);source_bytes=path.read_bytes()
|
|
230
|
+
plan=folder/(case+'.plan.json');out=folder/(case+'.jww')
|
|
231
|
+
receipt=ns['plan_building_replacement'](current,path,out,plan,archive_group=8,building_group=1,
|
|
232
|
+
source_reference_indices=[2,3],target_reference_indices=[2,3],distance_tolerance=.001,
|
|
233
|
+
angle_tolerance=5.00001 if angle else 0.,angle_units='deg',settings_policy='destination')
|
|
234
|
+
assert not out.exists()
|
|
235
|
+
evidence=Path(receipt['path']).with_suffix('.groups.json')
|
|
236
|
+
measured=json.loads(evidence.read_text(encoding='utf-8'))['alignment']
|
|
237
|
+
assert measured['applied_scale']==1 and abs(measured['angle_difference_deg']+math.degrees(angle))<1e-8
|
|
238
|
+
assert 'measured_endpoint_displacement_mm' in measured
|
|
239
|
+
report=ns['run_approved'](receipt['path'],receipt['sha256'])
|
|
240
|
+
assert (report['modified_count'],report['added_count'],report['deleted_count'])==(1,1,0)
|
|
241
|
+
assert ns['check_replacement_groups'](out,evidence)['passed']
|
|
242
|
+
endpoints=CadDocument.open(out).line_points(4,units='real')
|
|
243
|
+
assert all(math.dist(a,b)<1e-6 for a,b in zip(endpoints,((0.,0.),(1000.,100.))))
|
|
244
|
+
assert path.read_bytes()==source_bytes and [p.read_bytes() for p in building_paths]==original_buildings
|
|
245
|
+
results.append('building_replacement_'+case+'_plan_approval_and_exact_saved_attributes')
|
|
246
|
+
rejected=CadDocument.open(new_building);rejected.set_group_scale(1,50)
|
|
247
|
+
different_scale=folder/'different_scale.jww';rejected.save(different_scale)
|
|
248
|
+
for case,path,angular in (('different_scale',different_scale,5.00001),('angle_exceeded',folder/'rotation_source.jww',4.9)):
|
|
249
|
+
plan=folder/(case+'_denied.plan.json');out=folder/(case+'_denied.jww')
|
|
250
|
+
try:
|
|
251
|
+
ns['plan_building_replacement'](current,path,out,plan,archive_group=8,building_group=1,
|
|
252
|
+
source_reference_indices=[2,3],target_reference_indices=[2,3],distance_tolerance=.001,
|
|
253
|
+
angle_tolerance=angular,angle_units='deg',settings_policy='destination')
|
|
254
|
+
except ValueError:pass
|
|
255
|
+
else:raise AssertionError('Unsupported scale/tolerance accepted')
|
|
256
|
+
assert not plan.exists() and not out.exists() and not plan.with_suffix('.groups.json').exists()
|
|
257
|
+
results.append('building_replacement_unequal_scale_and_angle_tolerance_refused')
|
|
258
|
+
cleanup=ns['plan_archive_cleanup'](folder/'replacement.jww',folder/'after_review.jww',folder/'cleanup.plan.json',archive_group=8)
|
|
259
|
+
assert not (folder/'after_review.jww').exists() and len(cleanup['plan']['targets'])==1
|
|
260
|
+
assert cleanup['sha256']!=replacement['sha256']
|
|
261
|
+
report=ns['run_approved'](cleanup['path'],cleanup['sha256'])
|
|
262
|
+
assert (report['deleted_count'],report['added_count'],report['modified_count'])==(1,0,0)
|
|
263
|
+
before=CadDocument.open(folder/'replacement.jww');after=CadDocument.open(folder/'after_review.jww')
|
|
264
|
+
assert [ns['view'](e) for e in before.entities if e.attributes['layer_group']!=8]==[ns['view'](e) for e in after.entities]
|
|
265
|
+
results.append('separate_archive_cleanup_plan_after_synthetic_review')
|
|
266
|
+
sx=ns['read_sxf_report'](source)
|
|
267
|
+
assert sx['colors']==cad.get_sxf_colors() and sx['linetypes']==cad.get_sxf_linetypes()
|
|
268
|
+
assert cad.get_sxf_color('red')['code']==102
|
|
269
|
+
results.append('sxf_named_definition_reading')
|
|
270
|
+
# Execute the actual supplemental documentation on synthetic inputs.
|
|
271
|
+
from jwwkit.drawing_comparison import compare_drawings,write_comparison
|
|
272
|
+
from jwwkit.jwf import JwfDocument
|
|
273
|
+
supplemental={}
|
|
274
|
+
for filename,expected_blocks in [('drawing_comparison.md',1),('jwf_readonly.md',1),('search_queries.md',1)]:
|
|
275
|
+
codes=re.findall(r'```python\n(.*?)\n```',(guides/filename).read_text(encoding='utf-8'),re.S)
|
|
276
|
+
assert len(codes)==expected_blocks
|
|
277
|
+
supplemental[filename]=len(codes)
|
|
278
|
+
if filename=='drawing_comparison.md':
|
|
279
|
+
(folder/'before.jww').write_bytes(source.read_bytes())
|
|
280
|
+
(folder/'after.jww').write_bytes((folder/'edited.jww').read_bytes())
|
|
281
|
+
elif filename=='jwf_readonly.md':
|
|
282
|
+
(folder/'settings.jwf').write_bytes(b'PCOLLOR_1=255 0 0 10 .4\r\n')
|
|
283
|
+
(folder/'other.jwf').write_bytes(b'PCOLLOR_1=0 0 0 10 .4\r\n')
|
|
284
|
+
(folder/'drawing.jww').write_bytes(source.read_bytes())
|
|
285
|
+
example={}
|
|
286
|
+
with contextlib.chdir(folder):
|
|
287
|
+
for code in codes:exec(compile(code,filename,'exec'),example)
|
|
288
|
+
if filename=='drawing_comparison.md':
|
|
289
|
+
assert example['result']['complete'] and (folder/'comparison.json').is_file()
|
|
290
|
+
assert example['result']['element_accounting']['left_selected']==3
|
|
291
|
+
elif filename=='jwf_readonly.md':
|
|
292
|
+
assert example['red']==255 and example['changes']['rows'][0]['status']=='different'
|
|
293
|
+
assert example['stored']['rows'][0]['status']=='not_comparable'
|
|
294
|
+
else:
|
|
295
|
+
from jwwkit.safe_edit import execute_plan
|
|
296
|
+
querydoc=CadDocument.open(template_file);querydoc.set_group_scale(0,100)
|
|
297
|
+
querydoc.add_line((0,0),(20,0),layer=3)
|
|
298
|
+
querydoc.add_arc((0,0),10,0,2,layer=5)
|
|
299
|
+
querydoc.add_text((0,0),content='AW-01')
|
|
300
|
+
querysource=folder/'query_input.jww';querydoc.save(querysource)
|
|
301
|
+
assert example['inspect_drawing'](querysource)['long_shapes']==(0,1)
|
|
302
|
+
assert example['inspect_drawing'](querysource)['aw_texts']==(2,)
|
|
303
|
+
receipt=example['plan_search_move'](querysource,folder/'query_output.jww',folder/'query_review.json')
|
|
304
|
+
assert receipt['plan']['schema_version']==3
|
|
305
|
+
execution=execute_plan(receipt['path'],approved_sha256=receipt['sha256'])
|
|
306
|
+
assert execution['modified_count']==2 and execution['saved_all_fields_match']
|
|
307
|
+
assert querydoc.search_entities({'field':'text','match':'regex','value':'^AW-'})['match_count']==1
|
|
308
|
+
results.append('json_query_documented_plan_and_isolated_regex')
|
|
309
|
+
scoped=compare_drawings(current,folder/'replacement.jww',left_scope={'groups':[1]},right_scope={'groups':[1]})
|
|
310
|
+
assert scoped['complete'] and (scoped['summary']['added'],scoped['summary']['deleted'])==(1,1)
|
|
311
|
+
execution=json.loads((folder/'replacement.changes.json').read_text(encoding='utf-8'))
|
|
312
|
+
assert (execution['modified_count'],execution['added_count'],execution['deleted_count'])==(1,1,0)
|
|
313
|
+
assert execution['saved_all_fields_match'] and execution['approved_plan_sha256']==replacement['sha256']
|
|
314
|
+
results.append('independent_comparison_docs_group_scope_versus_execution_record')
|
|
315
|
+
compound=CadDocument.open(dimsource)
|
|
316
|
+
compound.add_solid(((20,0),(30,10),(30,0),(20,10)))
|
|
317
|
+
compound.add_circle_solid((40,0),5)
|
|
318
|
+
definition=compound.add_block_definition('CHECK',[Line((0,0),(10,0))])
|
|
319
|
+
compound.place_block(definition.number,(60,0))
|
|
320
|
+
compound_path=folder/'compound.jww';compound.save(compound_path)
|
|
321
|
+
compound_bytes=compound_path.read_bytes();r=compare_drawings(compound_path,compound_path)
|
|
322
|
+
assert r['complete'] and r['summary']['unchanged']==len(compound.entities)
|
|
323
|
+
assert {x['type'] for x in r['left_entities']}>={'DIMENSION','BLOCK','SOLID','CIRCLE_SOLID'}
|
|
324
|
+
assert compound_path.read_bytes()==compound_bytes and r['definitions']['matches'][0]['semantic_equal']
|
|
325
|
+
results.append('compound_comparison_dimensions_blocks_solids')
|
|
326
|
+
# Fully synthetic compatibility fixtures: no manufacturer DXF/JWW is packaged.
|
|
327
|
+
from jwwkit.reader.archive import read_records_bytes,RecordError
|
|
328
|
+
from jwwkit.writer.preserved import serialize_body
|
|
329
|
+
template=(template_file).read_bytes()
|
|
330
|
+
end=read_records_bytes(template)['header_end']
|
|
331
|
+
for version in (600,700):
|
|
332
|
+
header=bytearray(template[:end]);struct.pack_into('<I',header,8,version)
|
|
333
|
+
records=[Line((0,0),(10,0)).record(),Text((0,0),(5,0),'synthetic').record(),
|
|
334
|
+
Circle((5,5),2).record(),Circle((10,10),3,is_full_circle=False,arc_angle=1).record()]
|
|
335
|
+
raw=bytes(header)+serialize_body(records,[],version)
|
|
336
|
+
declarations=read_records_bytes(raw)['class_declarations']
|
|
337
|
+
fixture=bytearray(raw)
|
|
338
|
+
for declaration in declarations:struct.pack_into('<H',fixture,declaration['offset']+2,0)
|
|
339
|
+
source0=folder/f'schema0_{version}.jww';source0.write_bytes(fixture)
|
|
340
|
+
try:CadDocument.open(source0)
|
|
341
|
+
except RecordError as exc:assert 'schema_profile="rebro_schema0"' in str(exc)
|
|
342
|
+
else:raise AssertionError('Default reader accepted schema0')
|
|
343
|
+
readonly=CadDocument.open(source0,schema_profile='rebro_schema0')
|
|
344
|
+
target0=folder/f'schema0_{version}_copy.jww';readonly.save(target0)
|
|
345
|
+
again=CadDocument.open(target0,schema_profile='rebro_schema0')
|
|
346
|
+
assert target0.read_bytes()==source0.read_bytes()
|
|
347
|
+
assert again.entities==readonly.entities
|
|
348
|
+
assert again._source_records['class_declarations']==readonly._source_records['class_declarations']
|
|
349
|
+
try:readonly.add_line((0,0),(1,0))
|
|
350
|
+
except ValueError as exc:assert 'read-only' in str(exc)
|
|
351
|
+
else:raise AssertionError('Schema0 editing was accepted')
|
|
352
|
+
results.append('explicit_schema0_readonly_file_roundtrip_v600_v700')
|
|
353
|
+
results.append('jwf_documentation_read_diff_no_settings_application')
|
|
354
|
+
verified=folder/'verified.jwf'
|
|
355
|
+
widths=' '.join(str(cad.get_text_preset(i)['size_x']) for i in range(1,11))
|
|
356
|
+
palette=cad.header['palette']['pen_colors'][1]
|
|
357
|
+
verified.write_bytes(('MWIDE='+widths+'\nLCOLLOR_1='+f'{palette&255} {(palette>>8)&255} {(palette>>16)&255} 1\nKEY_A=5 1\n').encode('cp932'))
|
|
358
|
+
declaration=JwfDocument.open(verified);compared=declaration.compare_drawing(cad)
|
|
359
|
+
assert sum(x['status']=='same' for x in compared['rows'])==13
|
|
360
|
+
assert next(x for x in compared['rows'] if x['key']=='KEY_A')['status']=='not_comparable'
|
|
361
|
+
assert cad._source_bytes==original and not any(hasattr(declaration,n) for n in ('save','write','apply'))
|
|
362
|
+
results.append('jwf_proven_drawing_fields_only')
|
|
363
|
+
for tool,args,output in [(compare_command,['--left',str(current),'--right',str(folder/'replacement.jww'),'--left-groups','1','--right-groups','1'],'comparison_cli.json'),
|
|
364
|
+
(inspect_jwf_command,['--source',str(folder/'settings.jwf'),'--other',str(folder/'other.jwf'),'--drawing',str(source)],'jwf_cli.json')]:
|
|
365
|
+
proc=_run_tool(tool, [*args, '--output', str(folder/output)], folder)
|
|
366
|
+
assert proc.returncode==0,proc.stderr
|
|
367
|
+
assert (folder/output).is_file()
|
|
368
|
+
results.append('standalone_comparison_and_jwf_clis')
|
|
369
|
+
cli = _run_tool(summarize_command, ['--source', str(source), '--output', str(folder/'cli.json')], folder)
|
|
370
|
+
assert cli.returncode==0,cli.stderr
|
|
371
|
+
assert json.loads((folder/'cli.json').read_text(encoding='utf-8'))['entity_count']==4
|
|
372
|
+
assert source.read_bytes()==original
|
|
373
|
+
# Execute newer guides without relying on an original checkout or CAD.
|
|
374
|
+
new_guides={};workflow_checks=[]
|
|
375
|
+
for filename in ('read_view.md','geometry_view.md','spatial_search.md','workflow_settings.md','text_equivalence.md','edit_results.md','schema_normalization.md','reading_to_editing.md'):
|
|
376
|
+
codes=re.findall(r'```python\n(.*?)\n```',(guides/filename).read_text(encoding='utf-8'),re.S)
|
|
377
|
+
assert len(codes)==1;new_guides[filename]=len(codes);new_ns={}
|
|
378
|
+
with contextlib.chdir(folder):
|
|
379
|
+
for code in codes:exec(compile(code,filename,'exec'),new_ns)
|
|
380
|
+
if filename=='workflow_settings.md':workflow_ns=new_ns
|
|
381
|
+
if filename=='text_equivalence.md':equivalence_ns=new_ns
|
|
382
|
+
if filename=='edit_results.md':edit_ns=new_ns
|
|
383
|
+
if filename=='schema_normalization.md':normalization_ns=new_ns
|
|
384
|
+
if filename=='reading_to_editing.md':pipeline_ns=new_ns
|
|
385
|
+
policy_folder=folder/'job';policy_folder.mkdir();config=policy_folder/'jww_workflow.toml'
|
|
386
|
+
config.write_text('schema_version=1\n[building_replacement]\narchive_group="first_empty"\n',encoding='utf-8')
|
|
387
|
+
assert workflow_ns['read_policy'](policy_folder)['effective']['archive_group']=='first_empty'
|
|
388
|
+
kw=dict(work_dir=policy_folder,building_group=1,source_reference_indices=[2,3],target_reference_indices=[2,3],distance_tolerance=0,angle_tolerance=0,settings_policy='destination')
|
|
389
|
+
rec=workflow_ns['plan_replacement'](current,new_building,folder/'wf_archive.jww',folder/'wf_archive.json',**kw)
|
|
390
|
+
assert rec['plan']['schema_version']==4 and rec['plan']['workflow']['context']['archive_group']==4
|
|
391
|
+
config.write_text('schema_version=1\nprofile="quick"\n',encoding='utf-8')
|
|
392
|
+
report=workflow_ns['run_replacement_approved'](rec['path'],rec['sha256'])
|
|
393
|
+
assert report['workflow_audit']['passed'] and report['deleted_count']==0
|
|
394
|
+
workflow_checks.append('first_empty_fixed_and_changed_config_ignored')
|
|
395
|
+
rec=workflow_ns['plan_replacement'](current,new_building,folder/'wf_delete.jww',folder/'wf_delete.json',**kw)
|
|
396
|
+
config.unlink();report=workflow_ns['run_replacement_approved'](rec['path'],rec['sha256'])
|
|
397
|
+
assert report['workflow_audit']['passed'] and report['deleted_count']==1 and report['added_count']==1
|
|
398
|
+
assert report['workflow']['settings']['effective']['old_group_action']=='delete'
|
|
399
|
+
workflow_checks.append('quick_deletion_approved_plan_and_saved_group_audit')
|
|
400
|
+
config.write_text('schema_version=1\n[open]\nnormalize=true\n',encoding='utf-8')
|
|
401
|
+
try:workflow_ns['read_policy'](policy_folder)
|
|
402
|
+
except ValueError:pass
|
|
403
|
+
else:raise AssertionError('Future opening setting must be rejected')
|
|
404
|
+
assert len(CadDocument.open(current).entities)==4
|
|
405
|
+
assert [p.read_bytes()for p in building_paths]==original_buildings and source.read_bytes()==original
|
|
406
|
+
workflow_checks.append('unknown_open_settings_refused_no_implicit_read_originals_unchanged')
|
|
407
|
+
assert equivalence_ns['find_aw'](source)['complete']
|
|
408
|
+
common_plan=edit_ns['prepare_move'](source,folder/'common.jww',folder/'common.plan.json',[0],[10,0])
|
|
409
|
+
assert common_plan['status']=='planned'
|
|
410
|
+
common_preview=edit_ns['inspect_plan'](common_plan['plan']['path'],common_plan['plan']['sha256'])
|
|
411
|
+
assert common_preview['projected_counts']['modified']==1
|
|
412
|
+
common_saved=edit_ns['run_approved'](common_plan['plan']['path'],common_plan['plan']['sha256'])
|
|
413
|
+
assert common_saved['phase']=='verified'and common_saved['counts']['modified']==1 and source.read_bytes()==original
|
|
414
|
+
# Synthetic schema0 only; never normalize a user/manufacturer drawing here.
|
|
415
|
+
from jwwkit.reader.archive import read_records_bytes
|
|
416
|
+
from jwwkit.writer.preserved import serialize_body
|
|
417
|
+
normalization_checks=[]
|
|
418
|
+
config.write_text('schema_version=2\n[opening]\nrebro_schema0="normalize"\n',encoding='utf-8')
|
|
419
|
+
raw=source.read_bytes();header=raw[:read_records_bytes(raw)['header_end']]
|
|
420
|
+
normal=header+serialize_body([Line((0,0),(1,1)).record(),Text((0,0),(5,0),'合成').record(),Circle((2,2),3).record()],[],700,encoding='utf-16le')
|
|
421
|
+
data=bytearray(normal)
|
|
422
|
+
for declaration in read_records_bytes(normal)['class_declarations']:
|
|
423
|
+
struct.pack_into('<H',data,declaration['offset']+2,0)
|
|
424
|
+
schema0=folder/'schema0.jww';schema0.write_bytes(data)
|
|
425
|
+
standard=folder/'standard.jww';standard.write_bytes(normal)
|
|
426
|
+
planned=normalization_ns['prepare_normalization'](policy_folder,[schema0,standard],folder/'normalized',folder/'normalize.plan.json')
|
|
427
|
+
assert planned['plan']['schema_version']==6 and [e['status']for e in planned['plan']['entries']]==['normalize','not_needed']
|
|
428
|
+
config.write_text('schema_version=2\n[opening]\nrebro_schema0="reject"\n',encoding='utf-8')
|
|
429
|
+
if platform.system() == 'Windows':
|
|
430
|
+
completed=normalization_ns['run_normalization_approved'](planned['plan_path'],planned['plan_sha256'])
|
|
431
|
+
normalized=Path(completed['output_dir'])/completed['entries'][0]['output_name']
|
|
432
|
+
assert completed['verified'] and normalized.read_bytes()==normal and schema0.read_bytes()==bytes(data)
|
|
433
|
+
assert len(list(normalized.parent.glob('*.jww')))==1
|
|
434
|
+
normalization_checks.append('synthetic_schema_only_mixed_noop_frozen_settings')
|
|
435
|
+
edit=normalization_ns['prepare_separate_edit'](completed['result_path'],normalized,folder/'separate_edit.jww',folder/'separate_edit.plan.json',[0],[1,0])
|
|
436
|
+
assert edit['plan']['schema_version']in (1,2,3,4,5) and len(edit['plan']['dependencies'])>=4
|
|
437
|
+
assert not (folder/'separate_edit.jww').exists()
|
|
438
|
+
normalization_checks.append('separate_edit_plan_only_with_provenance_dependencies')
|
|
439
|
+
else:
|
|
440
|
+
normalization_checks.append('正規化の一括確定と確定結果に基づく編集計画: 対象外(Windows専用)')
|
|
441
|
+
# One synthetic workflow connects views, shape/point queries, U4 and U5.
|
|
442
|
+
pipeline_checks=[]
|
|
443
|
+
pipe=CadDocument.open(template_file)
|
|
444
|
+
pipe.set_group_scale(0,50);pipe.set_group_scale(1,25)
|
|
445
|
+
pipe.add_text((0,0),content='AW-01',units='real')
|
|
446
|
+
pipe.add_text((200,0),content='aw ‐02',units='real')
|
|
447
|
+
pipe.add_text((0,0),content='OTHER',layer_group=1,units='real')
|
|
448
|
+
pipe.add_line((0,100),(100,100),units='real')
|
|
449
|
+
pipe_source=folder/'pipeline.jww';pipe.save(pipe_source);pipe_before=pipe_source.read_bytes()
|
|
450
|
+
inspection=pipeline_ns['inspect_local'](pipe_source,[0,0],group=0,tie_tolerance=0,detail_limit=1)
|
|
451
|
+
assert inspection['text_match_count']==2 and inspection['text_search_complete']
|
|
452
|
+
assert inspection['details']['truncated'] and inspection['closest']['complete']
|
|
453
|
+
assert pipe_source.read_bytes()==pipe_before
|
|
454
|
+
pipeline_checks.append('views_geometry_text_frame_and_scoped_nearest_read_only')
|
|
455
|
+
planned=pipeline_ns['prepare_change'](pipe_source,folder/'pipeline_edited.jww',folder/'pipeline.plan.json',group=0,delta_real_mm=[10,0])
|
|
456
|
+
assert planned['status']=='planned'and planned['projected_counts']['modified']==2
|
|
457
|
+
plan_value=json.loads(Path(planned['plan']['path']).read_text(encoding='utf-8'))
|
|
458
|
+
assert plan_value['schema_version']==5 and plan_value['selection']is not None
|
|
459
|
+
assert not (folder/'pipeline_edited.jww').exists()
|
|
460
|
+
pipeline_checks.append('explicit_equivalence_query_fixed_in_schema5_plan')
|
|
461
|
+
saved=pipeline_ns['run_approved_change'](planned['plan']['path'],planned['plan']['sha256'])
|
|
462
|
+
assert saved['phase']=='verified'and saved['counts']['modified']==2 and saved['counts']['unchanged']==2
|
|
463
|
+
after=CadDocument.open(folder/'pipeline_edited.jww');before=CadDocument.open(pipe_source)
|
|
464
|
+
assert [e.geometry['content']for e in after.entities[:2]]==['AW-01','aw ‐02']
|
|
465
|
+
assert [e.raw for e in before.entities[2:]]==[e.raw for e in after.entities[2:]]
|
|
466
|
+
assert pipe_source.read_bytes()==pipe_before
|
|
467
|
+
pipeline_checks.append('approved_common_result_reload_original_text_and_non_targets_unchanged')
|
|
468
|
+
example=folder/'example_job';example.mkdir()
|
|
469
|
+
(example/'jww_workflow.toml').write_bytes(files('jwwkit').joinpath('resources', 'jww_workflow.example.toml').read_bytes())
|
|
470
|
+
from jwwkit.workflow_settings import load_workflow_settings,resolve_opening_settings
|
|
471
|
+
explicit=resolve_opening_settings(load_workflow_settings(example))
|
|
472
|
+
assert explicit['config_snapshot']['parsed']['schema_version']==2 and explicit['effective']['rebro_schema0']=='reject'
|
|
473
|
+
pipeline_checks.append('generic_settings_v2_example_defaults_to_reject')
|
|
474
|
+
# Loaded project modules must come from this distribution, never another checkout.
|
|
475
|
+
package_root = Path(jwwkit.__file__).resolve().parent
|
|
476
|
+
for name, module in tuple(sys.modules.items()):
|
|
477
|
+
path = getattr(module, "__file__", None)
|
|
478
|
+
if name.startswith("jwwkit.") and path:
|
|
479
|
+
assert Path(path).resolve().is_relative_to(package_root), path
|
|
480
|
+
results.append('standalone_summary_cli_and_module_origins')
|
|
481
|
+
return {'passed': True, 'python': platform.python_version(), 'guide_blocks_executed': len(blocks),
|
|
482
|
+
'supplemental_guide_blocks_executed': supplemental,
|
|
483
|
+
'new_guide_blocks_executed':new_guides,'workflow_checks':workflow_checks,'normalization_checks':normalization_checks,'pipeline_checks':pipeline_checks,'common_result_checks':['explicit_equivalence_query','schema5_preview_and_verified_common_result'],
|
|
484
|
+
'checks': results, 'package_files_checked': count, 'gui_executed': False, 'business_data_used': False}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Read-only independent JWW comparison; incomplete results exit with code 2."""
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import json
|
|
4
|
+
from jwwkit.drawing_comparison import compare_drawings,write_comparison
|
|
5
|
+
|
|
6
|
+
def add_arguments(p):
|
|
7
|
+
p.add_argument('--left',required=True,type=Path);p.add_argument('--right',required=True,type=Path)
|
|
8
|
+
p.add_argument('--output',required=True,type=Path)
|
|
9
|
+
p.add_argument('--left-groups',nargs='+',type=int);p.add_argument('--right-groups',nargs='+',type=int)
|
|
10
|
+
p.add_argument('--units',choices=('real','paper'),default='real')
|
|
11
|
+
p.add_argument('--position-tolerance',type=float,default=0)
|
|
12
|
+
p.add_argument('--angle-tolerance-degrees',type=float,default=0)
|
|
13
|
+
p.add_argument('--text-size-tolerance',type=float,default=0)
|
|
14
|
+
p.add_argument('--right-rotation-degrees',type=float,default=0)
|
|
15
|
+
p.add_argument('--right-translation',nargs=2,type=float,default=(0,0))
|
|
16
|
+
p.add_argument('--no-line-coverage',action='store_true')
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def run(a):
|
|
20
|
+
if a.output.exists():raise FileExistsError(a.output)
|
|
21
|
+
if a.output.suffix.lower()!='.json':raise ValueError('Output must be a new .json')
|
|
22
|
+
r=compare_drawings(a.left,a.right,left_scope={'groups':a.left_groups} if a.left_groups is not None else None,
|
|
23
|
+
right_scope={'groups':a.right_groups} if a.right_groups is not None else None,units=a.units,
|
|
24
|
+
position_tolerance=a.position_tolerance,angle_tolerance_degrees=a.angle_tolerance_degrees,
|
|
25
|
+
text_size_tolerance=a.text_size_tolerance,right_transform=dict(rotation_degrees=a.right_rotation_degrees,translation=a.right_translation),
|
|
26
|
+
line_coverage=not a.no_line_coverage)
|
|
27
|
+
write_comparison(r,a.output)
|
|
28
|
+
print(json.dumps(dict(complete=r['complete'],summary=r['summary']),ensure_ascii=False))
|
|
29
|
+
return 0 if r['complete'] else 2
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Local read-only JWF summary/differences; writes only a new diagnostic JSON."""
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import json
|
|
4
|
+
from jwwkit.jwf import JwfDocument
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def add_arguments(p):
|
|
8
|
+
p.add_argument('--source',required=True,type=Path);p.add_argument('--other',type=Path);p.add_argument('--drawing',type=Path)
|
|
9
|
+
p.add_argument('--encoding',default='cp932');p.add_argument('--preserve-invalid',action='store_true')
|
|
10
|
+
p.add_argument('--include-after-end',action='store_true',help='Compare declarations after END as documentation; never apply them')
|
|
11
|
+
p.add_argument('--output',required=True,type=Path)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def run(a):
|
|
15
|
+
if a.output.exists():raise FileExistsError(a.output)
|
|
16
|
+
options=dict(encoding=a.encoding,errors='preserve' if a.preserve_invalid else 'strict')
|
|
17
|
+
doc=JwfDocument.open(a.source,**options);report=dict(jwf=doc.summary())
|
|
18
|
+
if a.other:report['jwf_difference']=doc.diff(JwfDocument.open(a.other,**options),include_inactive=a.include_after_end)
|
|
19
|
+
if a.drawing:
|
|
20
|
+
from jwwkit.cad_model import CadDocument
|
|
21
|
+
report['drawing_comparison']=doc.compare_drawing(CadDocument.open(a.drawing))
|
|
22
|
+
# Surrogate escapes are recorded, not replaced or transmitted.
|
|
23
|
+
with a.output.open('x',encoding='utf-8') as f:json.dump(report,f,ensure_ascii=True,indent=2);f.write('\n')
|
|
24
|
+
print(json.dumps(dict(report=str(a.output),active_keys=len(report['jwf']['active_keys']),diagnostics=len(doc.diagnostics),settings_applied=False),ensure_ascii=True))
|