ruby3mf 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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>
@@ -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>
@@ -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="unbounded"/>
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="unbounded"/>
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 self.parse(zip_entry)
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', 'application/vnd.ms-package.3dmanufacturing-3dmodel+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
- # l.error "[Content_Types].xml:#{node.line} contains Default node without defined Extension attribute" unless node['Extension'].is_a? String
24
- # l.error "[Content_Types].xml:#{node.line} contains Default node with unexpected ContentType \"#{node['ContentType']}\"", page: 10 unless all_types.include? node['ContentType']
25
- l.info "Setting type hash #{node['Extension']}=#{node['ContentType']}"
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
- l.error :empty_override_part_name if node['PartName'].empty?
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[node['PartName']].nil?
33
- found_overrides[node['PartName']] = node['ContentType']
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
@@ -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[File.extname(part).strip.downcase[1..-1]]) }.each do |tfile|
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[rel[:type]]
140
+ relationship_type = RELATIONSHIP_TYPES[rel_type]
166
141
  if relationship_type.nil?
167
- l.error :invalid_relationship_type, type: rel[:type]
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],
@@ -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
-
@@ -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 has an invalid attribute type for the root 3D Model (StartPart). The required type is \"http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel\""
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
- invalid_relationship_type:
56
- msg: "Invalid relationship type '%{type}' specified in .rels relationship file. Parts in the 3D payload MUST use one of the appropriate relationship types to establish that relationship between two parts in the payload."
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
- missing_extension_in_content_types:
74
- msg: "Missing extension '%{ext}' in [Content_Types].xml"
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
@@ -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
- message = interpolate(error["msg"], options)
78
- @log_list << ["#{@context_stack.join("/")}", severity, message, options] unless severity==:debug && ENV['LOGDEBUG'].nil?
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.select { |i| levels.include? i[1] }
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
- doc_urls={
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
- to_hash.to_json
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
- mesh = find_child(object, "mesh")
28
- if mesh
29
- triangles = find_child(mesh, "triangles")
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.children.each do |triangle|
33
- v1 = triangle.attributes["v1"].to_s().to_i()
34
- v2 = triangle.attributes["v2"].to_s().to_i()
35
- v3 = triangle.attributes["v3"].to_s().to_i()
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
- has_object_material = object.attributes["pid"] and object.attributes["pindex"]
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.fatal_error :resource_3dmodel_orientation
70
+ l.error :resource_3dmodel_orientation
58
71
  elsif result == :hole
59
- l.fatal_error :resource_3dmodel_hole
72
+ l.error :resource_3dmodel_hole
60
73
  elsif result == :nonmanifold
61
- l.fatal_error :resource_3dmodel_nonmanifold
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
- root = model_doc.root
70
- node = root
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
- end
87
+
88
+ end