ruby3mf 0.2.3 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/PO_102_03.3mf +0 -0
- data/bin/batch.rb +3 -5
- data/bin/cli.rb +1 -4
- data/foo/2D/ffffa2c3-ba74-4bea-a4d0-167a4211134d.model +18747 -0
- data/foo/3D/3dmodel.model +40 -0
- data/foo/3D/_rels/3dmodel.model.rels +4 -0
- data/foo/Thumbnails/ffffa6c3-ba74-4bea-a4d0-167a4211134d.model +0 -0
- data/foo/[Content_Types].xml +7 -0
- data/foo/_rels/.rels +5 -0
- data/lib/ruby3mf/3MFcoreSpec_1.1.xsd.template +2 -2
- data/lib/ruby3mf/content_types.rb +42 -18
- data/lib/ruby3mf/document.rb +19 -33
- data/lib/ruby3mf/edge_list.rb +3 -4
- data/lib/ruby3mf/errors.yml +15 -9
- data/lib/ruby3mf/log3mf.rb +22 -31
- data/lib/ruby3mf/mesh_analyzer.rb +35 -32
- data/lib/ruby3mf/model3mf.rb +12 -33
- data/lib/ruby3mf/schema_files.rb +16 -2
- data/lib/ruby3mf/thumbnail3mf.rb +5 -0
- data/lib/ruby3mf/version.rb +1 -1
- data/lib/ruby3mf/xml_val.rb +1 -8
- data/suite.011917.out +237 -0
- metadata +10 -3
- data/bin/suite.rb +0 -50
@@ -0,0 +1,40 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<model unit="millimeter" xml:lang="en-US" xmlns="http://schemas.microsoft.com/3dmanufacturing/core/2015/02" xmlns:s="http://schemas.microsoft.com/3dmanufacturing/slice/2015/07" requiredextensions="s p" xmlns:p="http://schemas.microsoft.com/3dmanufacturing/production/2015/06">
|
3
|
+
<resources>
|
4
|
+
<s:slicestack id="3" zbottom="0.000">
|
5
|
+
<s:sliceref slicestackid="1" slicepath="/2D/ffffa2c3-ba74-4bea-a4d0-167a4211134d.model" />
|
6
|
+
</s:slicestack>
|
7
|
+
|
8
|
+
<object id="2" name="S11_cube_NA_Sliced" s:meshresolution="lowres" s:slicestackid="3" p:UUID="ffffa2c3-ba74-4bea-a4d0-167a4211134d">
|
9
|
+
<mesh>
|
10
|
+
<vertices>
|
11
|
+
<vertex x="100.001" y="100.000" z="100.000" />
|
12
|
+
<vertex x="100.001" y="0.000" z="100.000" />
|
13
|
+
<vertex x="100.001" y="100.000" z="0.000" />
|
14
|
+
<vertex x="0.000" y="100.000" z="0.000" />
|
15
|
+
<vertex x="100.001" y="0.000" z="0.000" />
|
16
|
+
<vertex x="0.000" y="0.000" z="0.000" />
|
17
|
+
<vertex x="0.000" y="0.000" z="100.000" />
|
18
|
+
<vertex x="0.000" y="100.000" z="100.000" />
|
19
|
+
</vertices>
|
20
|
+
<triangles>
|
21
|
+
<triangle v1="0" v2="1" v3="2" />
|
22
|
+
<triangle v1="3" v2="0" v3="2" />
|
23
|
+
<triangle v1="4" v2="3" v3="2" />
|
24
|
+
<triangle v1="5" v2="3" v3="4" />
|
25
|
+
<triangle v1="4" v2="6" v3="5" />
|
26
|
+
<triangle v1="6" v2="7" v3="5" />
|
27
|
+
<triangle v1="7" v2="6" v3="0" />
|
28
|
+
<triangle v1="1" v2="6" v3="4" />
|
29
|
+
<triangle v1="5" v2="7" v3="3" />
|
30
|
+
<triangle v1="7" v2="0" v3="3" />
|
31
|
+
<triangle v1="2" v2="1" v3="4" />
|
32
|
+
<triangle v1="0" v2="6" v3="1" />
|
33
|
+
</triangles>
|
34
|
+
</mesh>
|
35
|
+
</object>
|
36
|
+
</resources>
|
37
|
+
<build p:UUID="ab2ef9d9-5cb2-414c-bfed-a29e29e1f977">
|
38
|
+
<item objectid="2" transform="1.0000 0.0000 0.0000 0.0000 1.0000 0.0000 0.0000 0.0000 1.0000 30.0990 35.1000 30.1000" p:UUID="e0ad3d02-a9f2-47e7-b84f-12c588837f5b"/>
|
39
|
+
</build>
|
40
|
+
</model>
|
@@ -0,0 +1,4 @@
|
|
1
|
+
<?xml version="1.0"?>
|
2
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
3
|
+
<Relationship Target="/2D/ffffa2c3-ba74-4bea-a4d0-167a4211134d.model" Id="rel1" Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"/>
|
4
|
+
</Relationships>
|
Binary file
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<?xml version="1.0"?>
|
2
|
+
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
3
|
+
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" />
|
4
|
+
<Default Extension="model" ContentType="application/vnd.ms-package.3dmanufacturing-3dmodel+xml" />
|
5
|
+
<Default Extension="png" ContentType="image/png" />
|
6
|
+
<Override PartName="/Thumbnails/ffffa6c3-ba74-4bea-a4d0-167a4211134d.model" ContentType="image/png" />
|
7
|
+
</Types>
|
data/foo/_rels/.rels
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
<?xml version="1.0"?>
|
2
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
3
|
+
<Relationship Target="/3D/3dmodel.model" Id="rel0" Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"/>
|
4
|
+
<Relationship Target="/Thumbnails/ffffa6c3-ba74-4bea-a4d0-167a4211134d.model" Id="rel2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail"/>
|
5
|
+
</Relationships>
|
@@ -71,7 +71,7 @@ Items within this schema follow a simple naming convention of appending a prefix
|
|
71
71
|
<xs:sequence>
|
72
72
|
<xs:element ref="vertices"/>
|
73
73
|
<xs:element ref="triangles"/>
|
74
|
-
<xs:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="
|
74
|
+
<xs:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="30000"/>
|
75
75
|
</xs:sequence>
|
76
76
|
</xs:complexType>
|
77
77
|
<xs:complexType name="CT_Vertices">
|
@@ -87,7 +87,7 @@ Items within this schema follow a simple naming convention of appending a prefix
|
|
87
87
|
</xs:complexType>
|
88
88
|
<xs:complexType name="CT_Triangles">
|
89
89
|
<xs:sequence>
|
90
|
-
<xs:element ref="triangle" minOccurs="1" maxOccurs="
|
90
|
+
<xs:element ref="triangle" minOccurs="1" maxOccurs="30000"/>
|
91
91
|
</xs:sequence>
|
92
92
|
</xs:complexType>
|
93
93
|
<xs:complexType name="CT_Triangle">
|
@@ -1,36 +1,61 @@
|
|
1
1
|
class ContentTypes
|
2
2
|
|
3
|
-
def
|
4
|
-
found_types=
|
5
|
-
found_overrides=
|
3
|
+
def initialize(found={}, over={})
|
4
|
+
@found_types=found
|
5
|
+
@found_overrides=over
|
6
|
+
end
|
7
|
+
|
8
|
+
def size
|
9
|
+
@found_types.size + @found_overrides.size
|
10
|
+
end
|
11
|
+
|
12
|
+
def empty?
|
13
|
+
size == 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_type(target)
|
17
|
+
target = (target.start_with?('/') ? target : '/' + target).downcase
|
18
|
+
if @found_overrides[target]
|
19
|
+
content_type = @found_overrides[target]
|
20
|
+
else
|
21
|
+
extension = File.extname(target).strip.downcase[1..-1]
|
22
|
+
content_type = @found_types[extension]
|
23
|
+
end
|
24
|
+
content_type
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_types()
|
28
|
+
return @found_types.values + @found_overrides.values
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
6
32
|
|
33
|
+
def self.parse(zip_entry)
|
34
|
+
found_types = {}
|
35
|
+
found_overrides = {}
|
7
36
|
Log3mf.context "parse" do |l|
|
8
37
|
begin
|
9
|
-
|
10
38
|
doc = XmlVal.validate_parse(zip_entry)
|
11
39
|
|
12
40
|
l.warning '[Content_Types].xml must contain exactly one root node' unless doc.children.size == 1
|
13
41
|
l.warning '[Content_Types].xml must contain root name Types' unless doc.children.first.name == "Types"
|
14
42
|
|
15
|
-
required_content_types = ['application/vnd.openxmlformats-package.relationships+xml'
|
16
|
-
#optional_content_types = ['application/vnd.ms-printing.printticket+xml']
|
17
|
-
#all_types = required_content_types + optional_content_types
|
43
|
+
required_content_types = ['application/vnd.openxmlformats-package.relationships+xml']
|
18
44
|
|
19
45
|
types_node = doc.children.first
|
20
46
|
types_node.children.each do |node|
|
21
47
|
l.context node.name do |l|
|
22
48
|
if node.name == 'Default'
|
23
|
-
|
24
|
-
|
25
|
-
l.
|
26
|
-
|
27
|
-
l.error :duplicate_content_extension_types if !found_types[node['Extension']].nil?
|
28
|
-
found_types[node['Extension']] = node['ContentType']
|
49
|
+
extension = node['Extension'].downcase
|
50
|
+
l.info "Setting type hash #{extension}=#{node['ContentType']}"
|
51
|
+
l.error :duplicate_content_extension_types if !found_types[extension].nil?
|
52
|
+
found_types[extension] = node['ContentType']
|
29
53
|
elsif node.name == 'Override'
|
30
|
-
|
54
|
+
part_name = node['PartName'].downcase
|
55
|
+
l.error :empty_override_part_name if part_name.empty?
|
31
56
|
|
32
|
-
l.error :duplicate_content_override_types if !found_overrides[
|
33
|
-
found_overrides[
|
57
|
+
l.error :duplicate_content_override_types if !found_overrides[part_name].nil?
|
58
|
+
found_overrides[part_name] = node['ContentType']
|
34
59
|
else
|
35
60
|
l.warning "[Content_Types].xml:#{node.line} contains unexpected element #{node.name}", page: 10
|
36
61
|
end
|
@@ -43,7 +68,6 @@ class ContentTypes
|
|
43
68
|
l.error "[Content_Types].xml file is not valid XML. #{e}", page: 15
|
44
69
|
end
|
45
70
|
end
|
46
|
-
|
47
|
-
found_types
|
71
|
+
return new(found_types, found_overrides)
|
48
72
|
end
|
49
73
|
end
|
data/lib/ruby3mf/document.rb
CHANGED
@@ -19,20 +19,12 @@ class Document
|
|
19
19
|
THUMBNAIL_TYPES = %w[image/jpeg image/png].freeze
|
20
20
|
TEXTURE_TYPES = %w[image/jpeg image/png application/vnd.ms-package.3dmanufacturing-3dmodeltexture].freeze
|
21
21
|
|
22
|
-
# Relationship to valid Content types
|
23
|
-
REL_TO_CONTENT_TYPES = {
|
24
|
-
MODEL_TYPE => 'application/vnd.ms-package.3dmanufacturing-3dmodel+xml',
|
25
|
-
PRINT_TICKET_TYPE => 'application/vnd.ms-printing.printticket+xml',
|
26
|
-
TEXTURE_TYPE => TEXTURE_TYPES,
|
27
|
-
THUMBNAIL_TYPE => THUMBNAIL_TYPES
|
28
|
-
}
|
29
|
-
|
30
22
|
# Relationship Type => Class validating relationship type
|
31
23
|
RELATIONSHIP_TYPES = {
|
32
|
-
MODEL_TYPE => {klass: 'Model3mf', collection: :models},
|
33
|
-
THUMBNAIL_TYPE => {klass: 'Thumbnail3mf', collection: :thumbnails},
|
34
|
-
TEXTURE_TYPE => {klass: 'Texture3mf', collection: :textures},
|
35
|
-
PRINT_TICKET_TYPE => {}
|
24
|
+
MODEL_TYPE => {klass: 'Model3mf', collection: :models, valid_types: ['application/vnd.ms-package.3dmanufacturing-3dmodel+xml']},
|
25
|
+
THUMBNAIL_TYPE => {klass: 'Thumbnail3mf', collection: :thumbnails, valid_types: THUMBNAIL_TYPES},
|
26
|
+
TEXTURE_TYPE => {klass: 'Texture3mf', collection: :textures, valid_types: TEXTURE_TYPES},
|
27
|
+
PRINT_TICKET_TYPE => {valid_types: ['application/vnd.ms-printing.printticket+xml']}
|
36
28
|
}
|
37
29
|
|
38
30
|
def initialize(zip_filename)
|
@@ -41,7 +33,7 @@ class Document
|
|
41
33
|
self.textures=[]
|
42
34
|
self.objects={}
|
43
35
|
self.relationships={}
|
44
|
-
self.types=
|
36
|
+
self.types=nil
|
45
37
|
self.parts=[]
|
46
38
|
@zip_filename = zip_filename
|
47
39
|
end
|
@@ -49,7 +41,7 @@ class Document
|
|
49
41
|
#verify that each texture part in the 3MF is related to the model through a texture relationship in a rels file
|
50
42
|
def self.validate_texture_parts(document, log)
|
51
43
|
unless document.types.empty?
|
52
|
-
document.parts.select { |part| TEXTURE_TYPES.include?(document.types
|
44
|
+
document.parts.select { |part| TEXTURE_TYPES.include?(document.types.get_type(part)) }.each do |tfile|
|
53
45
|
if document.textures.select { |f| f[:target] == tfile }.size == 0
|
54
46
|
if document.thumbnails.select { |t| t[:target] == tfile }.size == 0
|
55
47
|
log.context "part names" do |l|
|
@@ -105,10 +97,6 @@ class Document
|
|
105
97
|
content_type_match = zip_file.glob('\[Content_Types\].xml').first
|
106
98
|
if content_type_match
|
107
99
|
m.types = ContentTypes.parse(content_type_match)
|
108
|
-
model_extension = m.types.key('application/vnd.ms-package.3dmanufacturing-3dmodel+xml')
|
109
|
-
model_extension = model_extension.downcase unless model_extension.nil?
|
110
|
-
model_file = zip_file.glob("**/*.#{model_extension}").first
|
111
|
-
l.error :no_3d_model, extension: model_extension if model_file.nil?
|
112
100
|
else
|
113
101
|
l.fatal_error 'Missing required file: [Content_Types].xml', page: 4
|
114
102
|
end
|
@@ -146,26 +134,24 @@ class Document
|
|
146
134
|
l.error :err_uri_empty_segment if target.end_with? '/' or target.include? '//'
|
147
135
|
l.error :err_uri_relative_path if target.include? '/../'
|
148
136
|
relationship_file = zip_file.glob(target).first
|
149
|
-
|
150
|
-
# check that relationships are valid; extensions and relationship types must jive
|
151
|
-
extension = File.extname(target).strip.downcase[1..-1]
|
152
|
-
|
153
|
-
content_type = m.types[extension]
|
154
137
|
rel_type = rel[:type]
|
155
|
-
expected_content_type = REL_TO_CONTENT_TYPES[rel_type]
|
156
|
-
|
157
|
-
if (expected_content_type)
|
158
|
-
l.error :missing_extension_in_content_types, ext: extension unless content_type
|
159
|
-
l.error :resource_contentype_invalid, bt: content_type, rt: rel[:target] unless (!content_type.nil? && expected_content_type.include?(content_type))
|
160
|
-
else
|
161
|
-
l.info "found unrecognized relationship type: #{rel_type}"
|
162
|
-
end
|
163
138
|
|
164
139
|
if relationship_file
|
165
|
-
relationship_type = RELATIONSHIP_TYPES[
|
140
|
+
relationship_type = RELATIONSHIP_TYPES[rel_type]
|
166
141
|
if relationship_type.nil?
|
167
|
-
l.
|
142
|
+
l.warning :unsupported_relationship_type, type: rel[:type], target: rel[:target]
|
168
143
|
else
|
144
|
+
# check that relationships are valid; extensions and relationship types must jive
|
145
|
+
content_type = m.types.get_type(target)
|
146
|
+
expected_content_type = relationship_type[:valid_types]
|
147
|
+
|
148
|
+
if (expected_content_type)
|
149
|
+
l.error :missing_content_type, part: target unless content_type
|
150
|
+
l.error :resource_contentype_invalid, bt: content_type, rt: rel[:target] unless (content_type.nil? || expected_content_type.include?(content_type))
|
151
|
+
else
|
152
|
+
l.info "found unrecognized relationship type: #{rel_type}"
|
153
|
+
end
|
154
|
+
|
169
155
|
unless relationship_type[:klass].nil?
|
170
156
|
m.send(relationship_type[:collection]) << {
|
171
157
|
rel_id: rel[:id],
|
data/lib/ruby3mf/edge_list.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
class EdgeList
|
2
2
|
|
3
|
-
def initialize
|
3
|
+
def initialize
|
4
4
|
@edges = { }
|
5
5
|
end
|
6
6
|
|
@@ -34,14 +34,14 @@ class EdgeList
|
|
34
34
|
@edges[edge]
|
35
35
|
end
|
36
36
|
|
37
|
-
def print_list
|
37
|
+
def print_list
|
38
38
|
@edges.each do |key, value|
|
39
39
|
(pos, neg) = value
|
40
40
|
puts "#{pos} : #{neg}"
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
def verify_edges
|
44
|
+
def verify_edges
|
45
45
|
@edges.each do |key, value|
|
46
46
|
(pos, neg) = value
|
47
47
|
|
@@ -58,4 +58,3 @@ class EdgeList
|
|
58
58
|
end
|
59
59
|
|
60
60
|
end
|
61
|
-
|
data/lib/ruby3mf/errors.yml
CHANGED
@@ -47,13 +47,13 @@ invalid_content_type:
|
|
47
47
|
msg: "[Content_Types].xml is missing required ContentType \"application/vnd.openxmlformats-package.relationships+xml\""
|
48
48
|
page: 10
|
49
49
|
invalid_startpart_type:
|
50
|
-
msg: "rels/.rels Relationship file
|
50
|
+
msg: "rels/.rels Relationship file is missing a required StartPart relationship to the primary 3D payload"
|
51
51
|
page: 10
|
52
52
|
invalid_startpart_target:
|
53
53
|
msg: "Invalid StartPart target '%{target}'. The 3MF Document StartPart relationship MUST point to the 3D Model part that identifies the root of the 3D payload."
|
54
54
|
page: 10
|
55
|
-
|
56
|
-
msg: "
|
55
|
+
unsupported_relationship_type:
|
56
|
+
msg: "Validation of relationship type '%{type}' is not supported by this tool. The targeted part '%{target}' will not be validated."
|
57
57
|
page: 10
|
58
58
|
invalid_thumbnail_file:
|
59
59
|
msg: "thumbnail file must be valid image file"
|
@@ -70,8 +70,8 @@ invalid_texture_file_type:
|
|
70
70
|
missing_content_types:
|
71
71
|
msg: "Missing required file: [Content_Types].xml"
|
72
72
|
page: 4
|
73
|
-
|
74
|
-
msg: "
|
73
|
+
missing_content_type:
|
74
|
+
msg: "Unable to find an associated content type for part '%{part}' in [Content_Types].xml"
|
75
75
|
page: 10
|
76
76
|
missing_dot_rels_file:
|
77
77
|
msg: "Missing required file _rels/.rels"
|
@@ -86,9 +86,6 @@ non_unique_rel_id:
|
|
86
86
|
multiple_relationships:
|
87
87
|
msg: "There MUST NOT be more than one relationship of a given relationship type from one part to a second part"
|
88
88
|
page: 10
|
89
|
-
no_3d_model:
|
90
|
-
msg: "Required 3D model payload not found with provided Content Type model extension: .%{extension}"
|
91
|
-
page: 10
|
92
89
|
not_a_zip:
|
93
90
|
msg: "File provided is not a valid ZIP archive"
|
94
91
|
page: 9
|
@@ -166,4 +163,13 @@ duplicate_content_override_types:
|
|
166
163
|
page: 8
|
167
164
|
empty_override_part_name:
|
168
165
|
msg: "Overrides can't have empty partname"
|
169
|
-
page: 8
|
166
|
+
page: 8
|
167
|
+
not_enough_triangles:
|
168
|
+
msg: "Mesh has fewer than four triangles"
|
169
|
+
page: 30
|
170
|
+
has_base_materials_gradient:
|
171
|
+
msg: "Base materials form a gradient on one or more triangles. Interpolation of materials is not supported in the core spec."
|
172
|
+
page: 31
|
173
|
+
thumbnail_image_type_mismatch:
|
174
|
+
msg: "Image not of declared type"
|
175
|
+
page: 36
|
data/lib/ruby3mf/log3mf.rb
CHANGED
@@ -27,15 +27,24 @@ class Log3mf
|
|
27
27
|
|
28
28
|
LOG_LEVELS = [:fatal_error, :error, :warning, :info, :debug]
|
29
29
|
|
30
|
+
SPEC_LINKS = {
|
31
|
+
core: 'http://3mf.io/wp-content/uploads/2016/03/3MFcoreSpec_1.1.pdf',
|
32
|
+
material: 'http://3mf.io/wp-content/uploads/2015/04/3MFmaterialsSpec_1.0.1.pdf',
|
33
|
+
production: 'http://3mf.io/wp-content/uploads/2016/07/3MFproductionSpec.pdf',
|
34
|
+
slice: 'http://3mf.io/wp-content/uploads/2016/07/3MFsliceSpec.pdf',
|
35
|
+
#opc: 'http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-376,%20Fourth%20Edition,%20Part%202%20-%20Open%20Packaging%20Conventions.zip'
|
36
|
+
opc: 'http://3mf.io/wp-content/uploads/2016/03/3MFcoreSpec_1.1.pdf'
|
37
|
+
}.freeze
|
38
|
+
|
30
39
|
# Allows us to throw FatalErrors if we ever get errors of severity :fatal_error
|
31
40
|
class FatalError < RuntimeError
|
32
41
|
end
|
33
42
|
|
34
|
-
def initialize
|
43
|
+
def initialize
|
35
44
|
@log_list = []
|
36
45
|
@context_stack = []
|
37
46
|
@ledger = []
|
38
|
-
errormap_path = File.join(File.dirname(__FILE__),"errors.yml")
|
47
|
+
errormap_path = File.join(File.dirname(__FILE__), "errors.yml")
|
39
48
|
@errormap = YAML.load_file(errormap_path)
|
40
49
|
end
|
41
50
|
|
@@ -50,10 +59,7 @@ class Log3mf
|
|
50
59
|
|
51
60
|
def context (context_description, &block)
|
52
61
|
@context_stack.push(context_description)
|
53
|
-
#puts "started context #{@context_stack.join("/")}"
|
54
|
-
|
55
62
|
retval = block.call(Log3mf.instance)
|
56
|
-
|
57
63
|
@context_stack.pop
|
58
64
|
retval
|
59
65
|
end
|
@@ -71,11 +77,15 @@ class Log3mf
|
|
71
77
|
end
|
72
78
|
|
73
79
|
def log(severity, message, options = {})
|
74
|
-
error = @errormap.fetch(message.to_s) { {"msg" => message.to_s, "page" => nil
|
80
|
+
error = @errormap.fetch(message.to_s) { {"msg" => message.to_s, "page" => nil} }
|
75
81
|
options[:page] = error["page"] unless options[:page]
|
76
82
|
options[:spec] = error["spec"] unless options[:spec]
|
77
|
-
|
78
|
-
|
83
|
+
entry = { id: message,
|
84
|
+
context: "#{@context_stack.join("/")}",
|
85
|
+
severity: severity,
|
86
|
+
message: interpolate(error["msg"], options) }
|
87
|
+
entry[:spec_ref] = spec_link(options[:spec], options[:page]) if (options && options[:page])
|
88
|
+
@log_list << entry
|
79
89
|
raise FatalError if severity == :fatal_error
|
80
90
|
end
|
81
91
|
|
@@ -88,7 +98,8 @@ class Log3mf
|
|
88
98
|
end
|
89
99
|
|
90
100
|
def entries(*levels)
|
91
|
-
@log_list
|
101
|
+
return @log_list if levels.size == 0
|
102
|
+
@log_list.select { |i| levels.include? i[:severity] }
|
92
103
|
end
|
93
104
|
|
94
105
|
def self.entries(*l)
|
@@ -97,31 +108,11 @@ class Log3mf
|
|
97
108
|
|
98
109
|
def spec_link(spec, page)
|
99
110
|
spec = :core unless spec
|
100
|
-
|
101
|
-
core: 'http://3mf.io/wp-content/uploads/2016/03/3MFcoreSpec_1.1.pdf',
|
102
|
-
material: 'http://3mf.io/wp-content/uploads/2015/04/3MFmaterialsSpec_1.0.1.pdf',
|
103
|
-
production: 'http://3mf.io/wp-content/uploads/2016/07/3MFproductionSpec.pdf',
|
104
|
-
slice: 'http://3mf.io/wp-content/uploads/2016/07/3MFsliceSpec.pdf',
|
105
|
-
#opc: 'http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-376,%20Fourth%20Edition,%20Part%202%20-%20Open%20Packaging%20Conventions.zip'
|
106
|
-
opc: 'http://3mf.io/wp-content/uploads/2016/03/3MFcoreSpec_1.1.pdf'
|
107
|
-
}
|
108
|
-
"#{doc_urls[spec]}#page=#{page}"
|
109
|
-
end
|
110
|
-
|
111
|
-
def to_hash
|
112
|
-
@log_list.collect { |ent|
|
113
|
-
h = { context: ent[0], severity: ent[1], message: ent[2] }
|
114
|
-
h[:spec_ref] = spec_link(ent[3][:spec], ent[3][:page]) if (ent[3] && ent[3][:page])
|
115
|
-
h
|
116
|
-
}
|
117
|
-
end
|
118
|
-
|
119
|
-
def self.to_hash
|
120
|
-
Log3mf.instance.to_hash
|
111
|
+
"#{SPEC_LINKS[spec]}#page=#{page}"
|
121
112
|
end
|
122
113
|
|
123
114
|
def to_json
|
124
|
-
|
115
|
+
@log_list.to_json
|
125
116
|
end
|
126
117
|
|
127
118
|
def self.to_json
|
@@ -11,7 +11,7 @@ end
|
|
11
11
|
|
12
12
|
class MeshAnalyzer
|
13
13
|
|
14
|
-
def self.validate_object(object)
|
14
|
+
def self.validate_object(object, includes_material)
|
15
15
|
Log3mf.context "verifying object" do |l|
|
16
16
|
children = object.children.map { |child| child.name }
|
17
17
|
have_override = object.attributes["pid"] or object.attributes["pindex"]
|
@@ -24,15 +24,30 @@ class MeshAnalyzer
|
|
24
24
|
# if a triangle has a pid, then the object needs a pid
|
25
25
|
has_triangle_pid = false
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
meshs = object.css('mesh')
|
28
|
+
meshs.each do |mesh|
|
29
|
+
|
30
|
+
triangles = mesh.css("triangle")
|
31
|
+
l.error :not_enough_triangles if triangles.count < 4
|
30
32
|
|
31
33
|
if triangles
|
32
|
-
triangles.
|
33
|
-
|
34
|
-
|
35
|
-
|
34
|
+
triangles.each do |triangle|
|
35
|
+
|
36
|
+
v1 = triangle.attributes["v1"].to_s.to_i
|
37
|
+
v2 = triangle.attributes["v2"].to_s.to_i
|
38
|
+
v3 = triangle.attributes["v3"].to_s.to_i
|
39
|
+
|
40
|
+
unless includes_material
|
41
|
+
l.context "validating property overrides" do |l|
|
42
|
+
property_overrides = []
|
43
|
+
property_overrides << triangle.attributes['p1'].to_s.to_i if triangle.attributes['p1']
|
44
|
+
property_overrides << triangle.attributes['p2'].to_s.to_i if triangle.attributes['p2']
|
45
|
+
property_overrides << triangle.attributes['p3'].to_s.to_i if triangle.attributes['p3']
|
46
|
+
|
47
|
+
property_overrides.reject! { |prop| prop.nil? }
|
48
|
+
l.error :has_base_materials_gradient unless property_overrides.uniq.size <= 1
|
49
|
+
end
|
50
|
+
end
|
36
51
|
|
37
52
|
if v1 == v2 || v2 == v3 || v3 == v1
|
38
53
|
l.error :non_distinct_indices
|
@@ -41,45 +56,33 @@ class MeshAnalyzer
|
|
41
56
|
list.add_edge(v1, v2)
|
42
57
|
list.add_edge(v2, v3)
|
43
58
|
list.add_edge(v3, v1)
|
44
|
-
|
45
|
-
if not has_triangle_pid
|
59
|
+
unless has_triangle_pid
|
46
60
|
has_triangle_pid = triangle.attributes["pid"] != nil
|
47
61
|
end
|
48
62
|
end
|
49
63
|
|
50
|
-
|
51
|
-
if has_triangle_pid and not has_object_material
|
64
|
+
if has_triangle_pid && !(object.attributes["pindex"] && object.attributes["pid"])
|
52
65
|
l.error :missing_object_pid
|
53
66
|
end
|
54
67
|
|
55
|
-
result = list.verify_edges
|
68
|
+
result = list.verify_edges
|
56
69
|
if result == :bad_orientation
|
57
|
-
l.
|
70
|
+
l.error :resource_3dmodel_orientation
|
58
71
|
elsif result == :hole
|
59
|
-
l.
|
72
|
+
l.error :resource_3dmodel_hole
|
60
73
|
elsif result == :nonmanifold
|
61
|
-
l.
|
74
|
+
l.error :resource_3dmodel_nonmanifold
|
62
75
|
end
|
76
|
+
|
63
77
|
end
|
64
78
|
end
|
65
79
|
end
|
66
80
|
end
|
67
81
|
|
68
|
-
def self.validate(model_doc)
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
if node.name == "model"
|
73
|
-
resources = find_child(node, "resources")
|
74
|
-
|
75
|
-
if resources
|
76
|
-
resources.children.each do |resource|
|
77
|
-
solid_model = resource.attributes["type"].to_s() == "model" or resource.attributes["type"].to_s() == "solidsupport"
|
78
|
-
if resource.name == "object" and solid_model
|
79
|
-
validate_object(resource)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
82
|
+
def self.validate(model_doc, includes_material)
|
83
|
+
model_doc.css('model/resources/object').select { |object| ['model', 'solidsupport', ''].include?(object.attributes['type'].to_s) }.each do |object|
|
84
|
+
validate_object(object, includes_material)
|
83
85
|
end
|
84
86
|
end
|
85
|
-
|
87
|
+
|
88
|
+
end
|